Transmit and Receive - GDMA

This sample demonstrates the functionality of sending and receiving data via CAN using GDMA.

The CAN controller is connected to a USB CAN analyser and communicates with the CAN controller by sending and receiving standard data frames, extended data frames, standard remote frames, extended remote frames and other frame types through the PC side.

Requirements

For hardware requirements, please refer to the Requirements.

Wiring

The EVB is connected to a TJA1051 CAN Receiver module by connecting P4_0 to CTX, P4_1 to CRX. The EVB BAT/5V is connected to the module VCC and NC. The EVB GND is connected to the module GND and S.

The other end of the TJA1051 CAN Receiver module is connected to the PC via a USB CAN analyzer, by connecting module CANH to analyzer CANH and module CANL to analyzer CANL.

../../../_images/CAN_Hardware_Connection_Diagram.png

CAN TRX Demo Code Hardware Connection Diagram

The introduction of CAN transceiver can refer to CAN Transceiver Introduction .

Configurations

  1. The following macros can be configured to modify CAN pin definitions.

    #define CAN_TX_PIN          P4_0
    #define CAN_RX_PIN          P4_1
    
  2. The following macros can be configured to modify TX/RX message buffer ID.

    #define TX_DMA_BUF_ID       0
    #define RX_DMA_BUF_ID       1
    
  3. The entry function is as follows, call this function in main() to run this sample code. For more details, please refer to the Initialization.

    can_trx_dma_demo();
    

Building and Downloading

For building and downloading, please refer to the Building and Downloading.

Experimental Verification

  1. Press the Reset button on the EVB and the message will be displayed in the Debug Analyzer.

    can_trx_dma_demo: start
    
  2. After the IC is reset, a data frame sent by the IC is received on the tool.

  3. The IC prints the log waiting to be received.

    can_driver_init: BUS state: 0, waiting...
    can_driver_init: BUS ON 1
    can_tx_dma_handler: TX DONE!
    can_dma_rx: waiting for rx...
    
  4. Send a data frame using the tool.

  5. The IC receives the data frame via GDMA and prints log.

    can_trx_handler: CAN RX
    can_trx_handler: MB_1 rx done
    can_rx_dma_handler: std id = 0x00000100, ext id = 0x00000000 dlc = 8
    can_rx_dma_handler: rx_data [0] 0x01
    can_rx_dma_handler: rx_data [1] 0x23
    can_rx_dma_handler: rx_data [2] 0x45
    can_rx_dma_handler: rx_data [3] 0x67
    can_rx_dma_handler: rx_data [4] 0x89
    can_rx_dma_handler: rx_data [5] 0xab
    can_rx_dma_handler: rx_data [6] 0xcd
    can_rx_dma_handler: rx_data [7] 0xef
    can_rx_dma_handler: waiting for rx...
    

Code Overview

This section introduces the code and process description for initialization and corresponding function implementation in the sample.

Source Code Directory

The directory for project file and source code are as follows.

  • For project directory, please refer to Source Code Directory.

  • Source code directory: sdk\sample\io_demo\canbus\trx_dma\can_trx_dma.c.

Initialization

The initialization flow for peripherals can refer to Initialization Flow.

CAN initialization flow is shown in the following figure.

../../../_images/can_init_flow.png

CAN Init Flow

  1. Call Pad_Config() and Pinmux_Config() to configure the corresponding pin’s PAD and PINMUX.

    void can_board_init(void)
    {
        /* Config pinmux and pad for CAN. */
    
        Pinmux_Config(CAN_TX_PIN, CAN_TX);
        Pinmux_Config(CAN_RX_PIN, CAN_RX);
    
        Pad_Config(CAN_TX_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_DISABLE, PAD_OUT_LOW);
        Pad_Config(CAN_RX_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_DISABLE, PAD_OUT_LOW);
    }
    
  2. Call RCC_PeriphClockCmd() to enable the CAN clock.

  3. Initialize the CAN peripheral.

    1. Define a CAN_InitTypeDef type init_struct and call CAN_StructInit() to pre-fill init_struct with default values.

    2. Modify the init_struct parameters as needed. The CAN initialization parameters are configured as shown in the table below.

    3. Call CAN_Init() to initialize the parameters and the CAN peripheral.

CAN Initialization Parameters

CAN Hardware Parameters

Setting in the init_struct

CAN

Auto Re-transmit Enable

CAN_InitTypeDef::CAN_AutoReTxEn

DISABLE

CAN Speed Parameter - BRP

CAN_BIT_TIMING_TYPE_TypeDef::can_brp

3

CAN Speed Parameter - SJW

CAN_BIT_TIMING_TYPE_TypeDef::can_sjw

3

CAN Speed Parameter - TSEG1

CAN_BIT_TIMING_TYPE_TypeDef::can_tseg1

13

CAN Speed Parameter - TSEG2

CAN_BIT_TIMING_TYPE_TypeDef::can_tseg2

4

RX GDMA Enable

CAN_InitTypeDef::CAN_RxDmaEn

ENABLE

  1. Call CAN_Cmd() to enable the corresponding CAN peripheral.

  2. Call CAN_INTConfig() to configure the CAN receive complete CAN_RX_INT interrupt, transmit complete CAN_TX_INT interrupt, error interrupt CAN_ERROR_INT, etc. Configure NVIC, and refer to Interrupt Configuration for NVIC-related configurations.

  3. Call CAN_GetBusState() to loop-check the CAN bus state and wait for the CAN bus to open.

    while (CAN_GetBusState() != CAN_BUS_STATE_ON)
    {
        __asm volatile
        (
            "nop    \n"
        );
    }
    

Note

If the program is stuck at the point of waiting for the CAN bus to be on, please check if the CAN bus is connected correctly.

  1. Call RCC_PeriphClockCmd() to enable the GDMA clock.

  2. Call GDMA_channel_request to request a free GDMA channel and register the GDMA interrupt handler.

  3. Initialize the GDMA peripheral.

    1. Define a GDMA_InitTypeDef type GDMA_InitStruct and execute GDMA_StructInit() to pre-fill GDMA_InitStruct with default values.

    2. Modify the GDMA_InitStruct parameters as needed. The initialization parameter configuration for GDMA TX and RX channels is shown in the following table. Execute GDMA_Init() to initialize the GDMA peripheral.

    3. Configure the GDMA total transfer complete interrupt GDMA_INT_Transfer and NVIC. Refer to Interrupt Configuration for related NVIC configurations.

GDMA Initialization Parameters

GDMA Hardware Parameters

Setting in the GDMA_InitStruct

GDMA TX Channel

GDMA RX Channel

Channel Num

GDMA_InitTypeDef::GDMA_ChannelNum

TX_DMA_CHANNEL_NUM

RX_DMA_CHANNEL_NUM

Transfer Direction

GDMA_InitTypeDef::GDMA_DIR

GDMA_DIR_MemoryToMemory

GDMA_DIR_PeripheralToMemory

Buffer Size

GDMA_InitTypeDef::GDMA_BufferSize

sizeof(CAN_RAM_TypeDef)

-

Source Address Increment or Decrement

GDMA_InitTypeDef::GDMA_SourceInc

DMA_SourceInc_Inc

DMA_SourceInc_Fix

Destination Address Increment or Decrement

GDMA_InitTypeDef::GDMA_DestinationInc

DMA_DestinationInc_Inc

DMA_DestinationInc_Inc

Source Data Size

GDMA_InitTypeDef::GDMA_SourceDataSize

GDMA_DataSize_Word

GDMA_DataSize_Word

Destination Data Size

GDMA_InitTypeDef::GDMA_DestinationDataSize

GDMA_DataSize_Word

GDMA_DataSize_Word

Source Burst Transaction Length

GDMA_InitTypeDef::GDMA_SourceMsize

GDMA_Msize_8

GDMA_Msize_1

Destination Burst Transaction Length

GDMA_InitTypeDef::GDMA_DestinationMsize

GDMA_Msize_8

GDMA_Msize_1

Source Address

GDMA_InitTypeDef::GDMA_SourceAddr

-

& (CAN0->CAN_RX_DMA_DATA)

Destination Address

GDMA_InitTypeDef::GDMA_DestinationAddr

(CAN0->CAN_RAM_DATA)

-

Source Handshake

GDMA_InitTypeDef::GDMA_SourceHandshake

-

GDMA_Handshake_CANBUS0_RX

Destination Handshake

GDMA_InitTypeDef::GDMA_DestHandshake

-

-

Note

During the initialization of the GDMA peripheral, do not initialize the source address of the TX GDMA channel and the destination address of the RX GDMA channel; these will be adjusted dynamically later.

Functional Implementation

CAN TX GDMA flow is shown in the following figure.

../../../_images/can_tx_dma_flow.png

CAN TX GDMA Flow

CAN RX GDMA flow is shown in the following figure.

../../../_images/can_rx_dma_flow.png

CAN RX GDMA Flow

  1. Call the can_dma_tx function to configure the value tx_can_ram_struct that needs to be set in the RAM register via TX GDMA.

  2. Call the can_start_tx_dma function.

    1. Set the source address of the TX GDMA channel to the address of tx_can_ram_struct.

    2. Enable the GDMA_INT_Transfer interrupt for TX GDMA.

    3. Enable the GDMA peripheral to start the transmission.

    static void can_start_tx_dma(uint8_t buf_id, CAN_RAM_TypeDef *p_can_ram_data)
    {
        CAN_MBTxINTConfig(CAN0, buf_id, ENABLE);
    
        GDMA_SetSourceAddress(TX_DMA_Channel, (uint32_t)p_can_ram_data);
        GDMA_INTConfig(TX_DMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
        GDMA_Cmd(TX_DMA_CHANNEL_NUM, ENABLE);
    }
    
  3. After enabling the GDMA TX channel, GDMA transfers data from tx_can_ram_struct to (CAN0->CAN_RAM_DATA). When TX is completed, a GDMA TX interrupt is triggered, and relevant information is printed.

    GDMA_INTConfig(TX_DMA_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);
    
    IO_PRINT_INFO0("can_tx_dma_handler: TX DONE!");
    
    GDMA_ClearINTPendingBit(TX_DMA_CHANNEL_NUM, GDMA_INT_Transfer);
    
  4. Call the can_dma_rx function.

    1. Mask the reception of frames rtr, ide, and id filtering, i.e., receive all frames.

    2. Enable RX GDMA and disable automatic reply.

    3. Configure the message buffer to receive mode using the set reception frame type.

    4. Enable message buffer reception interrupt and wait for the RAM status to be idle.

    ...
    rx_frame_type.msg_buf_id = RX_DMA_BUF_ID;
    rx_frame_type.frame_rtr_mask = CAN_RX_FRAME_MASK_RTR;
    rx_frame_type.frame_ide_mask = CAN_RX_FRAME_MASK_IDE;
    rx_frame_type.frame_id_mask = CAN_RX_FRAME_MASK_ID;
    rx_frame_type.rx_dma_en = SET;
    rx_frame_type.auto_reply_bit = RESET;
    rx_error = CAN_SetMsgBufRxMode(CAN0, &rx_frame_type);
    CAN_MBRxINTConfig(CAN0, rx_frame_type.msg_buf_id, ENABLE);
    while (CAN_GetRamState(CAN0) != CAN_RAM_STATE_IDLE)
    ...
    
  5. When the message buffer reception is complete, execute the interrupt handler function can_trx_handler.

    1. Call the can_start_rx_dma function to enable the configuration of the GDMA buffer size, configure the destination address of the GDMA RX channel, and enable RX GDMA transmission.

    if (dma_en_flag == ENABLE)
    {
        /* This value is fixed 4. */
        uint32_t dma_buffer_size = 4;
        can_start_rx_dma((uint32_t)&rx_dma_data_struct, dma_buffer_size);
    }
    
  6. After enabling the GDMA RX channel, when CAN receives data, the GDMA transfers the data from &(CAN0->CAN_RX_DMA_DATA) to rx_dma_data_struct. When the RX GDMA reception is complete, the interrupt handler function can_rx_dma_handler is executed.

    1. Disable the GDMA_INT_Transfer interrupt for RX GDMA.

    2. Enable the RX GDMA of the message buffer to start the next reception.

    3. Print the received frame ID and print the received frame data.

    4. Clear the GDMA_INT_Transfer interrupt flag for RX GDMA.

    GDMA_INTConfig(RX_DMA_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);
    CAN_SetMBnRxDmaEnFlag(CAN0, RX_DMA_BUF_ID, ENABLE);
    ...
    GDMA_ClearINTPendingBit(RX_DMA_CHANNEL_NUM, GDMA_INT_Transfer);