Transmit And Receive - GDMA

该示例演示了 CAN 通过 GDMA 收发数据的功能。

CAN 外接 USB CAN 分析仪,通过 PC 端收发标准数据帧,扩展数据帧,标准远程帧,扩展远程帧等帧类型与 CAN controller 进行通信。

芯片端通过 GDMA 接收 PC 下发的数据,并通过 GDMA 传输相同数据给 PC 端,PC 端收到芯片回的数据。

环境需求

该示例的环境需求,可参考 环境需求

硬件连线

EVB 外接 TJA1051 CAN Receiver 模块,连接 P3_2 和 CTX,P3_4 和 CRX,连接 EVB 3.3V 和模块 VCC,EVB VCC3.3 和连接模块 NC,连接 EVB GND 和模块 GND 和 S。

TJA1051 CAN Receiver 模块另一端通过 USB CAN 分析仪连接到 PC,连接模块 CANH 和分析仪 CANH,连接模块 CANL 和分析仪 CANL。

CAN 收发模块的介绍请参考 CAN 收发模块介绍

编译和下载

该示例的编译和下载流程,可参考 编译和下载

测试验证

  1. IC 复位后,工具上会收到 IC 发送的数据帧。

  2. IC 打印等待接收的 log:

    [CAN]  start can trx_gdma demo!
    [CAN]  BUS state: 0, waiting...
    [CAN]  BUS ON 1
    [TX GDMA HANDLER]  TX DONE!
    [CAN]  waiting for rx...
    
  3. 使用 tool 发送一个数据帧。

  4. IC 通过 GDMA 接收到数据帧,打印 log。

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

代码介绍

该章节主要介绍示例中的初始化和相应功能实现的代码和流程说明。

源码路径

工程文件和源码路径如下:

  • 工程路径: sdk\samples\peripheral\can\trx_dma\proj

  • 源码路径: sdk\samples\peripheral\can\trx_dma\src

初始化

外设的初始化流程可参考 General Introduction 中的 初始化流程 部分。

  1. 调用 Pad_Config()Pinmux_Config(),配置对应引脚的 PAD 和 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. 调用 RCC_PeriphClockCmd(),开启 CAN 时钟。

  3. 对 CAN 外设进行初始化:

    1. 定义 CAN_InitTypeDef 类型 init_struct,调用 CAN_StructInit()init_struct 预填默认值。

    2. 根据需求修改 init_struct 参数,CAN 的初始化参数配置如下表。

    3. 调用 CAN_Init(),初始化 CAN 外设。

CAN 初始化参数

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. 调用 CAN_Cmd(),使能相应 CAN 外设。

  2. 调用 CAN_INTConfig(),配置 CAN 接收完成 CAN_RX_INT 中断,发送完成 CAN_TX_INT 中断,错误中断 CAN_ERROR_INT 等。配置 NVIC,NVIC 相关配置可参考 中断配置

  3. 调用 CAN_GetBusState(),循环检查 CAN 总线状态,等待 CAN 总线打开。

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

备注

注意:如果程序卡在等待 CAN 总线打开处,请检查 CAN bus 是否连接准确。

  1. 调用 RCC_PeriphClockCmd() ,开启 GDMA 时钟。

  2. 对 GDMA 外设进行初始化:

    1. 定义 GDMA_InitTypeDef 类型 GDMA_InitStruct ,执行 GDMA_StructInit()GDMA_InitStruct 预填默认值。

    2. 根据需求修改 GDMA_InitStruct 参数。DMA TX 和 RX 通道的初始化参数配置如下表。执行 GDMA_Init(),初始化 GDMA 外设。

    3. 配置 GDMA 总传输完成中断 GDMA_INT_Transfer 和 NVIC,NVIC 相关配置可参考 中断配置

GDMA 初始化参数

GDMA Hardware Parameters

Setting in the GDMA_InitStruct

GDMA TX Channel

GDMA RX Channel

Channel Num

GDMA_InitTypeDef::GDMA_ChannelNum

1

0

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

-

& (CAN->CAN_RX_DMA_DATA)

Destination Address

GDMA_InitTypeDef::GDMA_DestinationAddr

(CAN->CAN_RAM_DATA)

-

Source Handshake

GDMA_InitTypeDef::GDMA_SourceHandshake

-

GDMA_Handshake_CAN_BUS_RX

Destination Handshake

GDMA_InitTypeDef::GDMA_DestHandshake

-

-

备注

GDMA 外设初始化时,暂不初始化 TX GDMA 通道的源端地址和 RX GDMA 通道的目的端地址,后续动态调整。

功能实现

  1. 调用 can_dma_tx 函数,配置需要通过 TX GDMA 配置到 RAM 寄存器的值 tx_can_ram_struct

  2. 调用 can_start_tx_dma 函数。

    1. 设置 TX GDMA 通道的源端地址为 tx_can_ram_struct 的地址。

    2. 使能 TX GDMA 的 GDMA_INT_Transfer 中断。

    3. 使能 GDMA 外设开始传输。

    void can_start_tx_dma(CAN_RAM_TypeDef *p_can_ram_data)
    {
        GDMA_SetSourceAddress(TX_GDMA_Channel, (uint32_t)p_can_ram_data);
        GDMA_INTConfig(TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
        GDMA_Cmd(TX_GDMA_CHANNEL_NUM, ENABLE);
    }
    
  3. 使能 GDMA TX 通道后,DMA 将数据从 tx_can_ram_struct 搬运到 (CAN->CAN_RAM_DATA)。 当 TX 完成时,会触发 GDMA TX 中断,打印相关信息。

    GDMA_INTConfig(TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);
    DBG_DIRECT("[TX DMA HANDLER]  TX DONE!");
    GDMA_ClearINTPendingBit(TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
    
  4. 调用 can_dma_rx 函数。

    1. 屏蔽接收帧 rtr,ide,id 过滤,即接收所有帧。

    2. 使能 Rx DMA,失能自动回复。

    3. 使用设置的接收帧类型将邮箱配置为接收模式。

    4. 使能邮箱接收中断,等待 RAM 状态空闲。

    ...
    rx_frame_type.msg_buf_id = RX_DMA_BUF_ID;
    rx_frame_type.frame_rtr_mask = SET;
    rx_frame_type.frame_ide_mask = SET;
    rx_frame_type.frame_id_mask = CAN_FRAME_ID_MASK_MAX_VALUE;
    rx_frame_type.rx_dma_en = SET;
    rx_frame_type.auto_reply_bit = RESET;
    rx_error = CAN_SetMsgBufRxMode(&rx_frame_type);
    CAN_MBRxINTConfig(rx_frame_type.msg_buf_id, ENABLE);
    while (CAN_GetRamState() != CAN_RAM_STATE_IDLE)
    ...
    
  5. 当 message buffer 接收完成时,执行中断处理函数 CAN_Handler

    1. 调用 CAN_GetRxDmaMsize() 函数获取 Rx GDMA buffer size。

    2. 调用 can_start_rx_dma 函数使能配置 GDMA buffer size,配置 GDMA RX 通道的目的端地址,使能 RX GDMA 传输。

    if (dma_en_flag == ENABLE)
    {
        uint32_t dma_buffer_size = CAN_GetRxDmaMsize();
        can_start_rx_dma((uint32_t)&rx_dma_data_struct, dma_buffer_size);
    }
    
    ...
    
    GDMA_SetBufferSize(RX_GDMA_Channel, buffer_size);
    GDMA_SetDestinationAddress(RX_GDMA_Channel, des_addr);
    GDMA_INTConfig(RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
    GDMA_Cmd(RX_GDMA_CHANNEL_NUM, ENABLE);
    
  6. 使能 GDMA RX 通道后,当 CAN 接收到数据后,DMA 将数据从 &(CAN->CAN_RX_DMA_DATA) 搬运到 rx_dma_data_struct。当 RX GDMA 接收完成时,执行中断处理函数 RX_GDMA_Channel_Handler

    1. 失能 RX GDMA 的 GDMA_INT_Transfer 中断。

    2. 使能 message buffer 的 RX GDMA 开始下一次接收。

    3. 打印接收到的帧 ID,打印接收帧数据。

    4. 清除 RX GDMA 的 GDMA_INT_Transfer 中断标志位。

    GDMA_INTConfig(RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);
    ...
    CAN_SetMBnRxDmaEnFlag(RX_DMA_BUF_ID, ENABLE);
    ...
    DBG_DIRECT(...);
    ...
    GDMA_ClearINTPendingBit(RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);