Transmit And Recieve - DMA

该示例演示了 CAN 通过DMA收发数据的功能。 CAN外接USB CAN FD分析仪,通过PC端收发数据与CAN controller进行通信,使用CAN通过DMA接收或发送数据帧。

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL87x2G HDK

RTL87x2G EVB

更多信息请参考 快速入门

硬件连线

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 FD分析仪连接到PC,连接模块CANH和分析仪CANH,连接模块CANL和分析仪CANL。

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

编译和下载

该示例的工程路径如下:

Project file: samples\peripheral\can\trx_dma\proj\rtl87x2g\mdk

Project file: samples\peripheral\can\trx_dma\proj\rtl87x2g\gcc

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MPTool 给出的步骤将app bin烧录至EVB内。

  5. 按下复位按键,开始运行。

测试验证

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

这里应该是工具上收到IC发送的数据帧图片

IC发送的数据帧

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

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

这里应该是工具上发送数据帧的图片

Tool发送的数据帧

  1. IC通过DMA接收到数据帧,打印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...
    

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

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

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

该工程的工程文件代码结构如下:

└── Project: trx_dma
    └── secure_only_app
        └── Device                   includes startup code
            ├── startup_rtl.c
            └── system_rtl.c
        ├── CMSIS                    includes CMSIS header files
        ├── CMSE Library             Non-secure callable lib
        ├── Lib                      includes all binary symbol files that user application is built on
            └── rtl87x2g_io.lib
        ├── Peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl_rcc.c
            ├── rtl_pinmux.c
            ├── rtl_nvic.c
            ├── rtl_gdma.c
            └── rtl_can.c
        └── APP                      includes the ble_peripheral user application implementation
            ├── main_ns.c
            └── io_can.c

初始化

初始化流程包括了 can_board_initcan_driver_initrx_gdma_driver_inittx_gdma_driver_init


can_board_init 包含了对PAD和PINMUX的设置。

  1. 配置PAD:设置引脚、PINMUX模式、PowerOn、无内部上拉。

  2. 配置PINMUX:分配引脚分别为CAN_TX、CAN_RX功能。


can_driver_init 包含了对CAN外设的初始化。

  1. 使能PCC时钟。

  2. 关闭CAN的自动重传模式。

  3. 设置CAN总线同步位时间,将CAN总线速度设置为500kHz。

  4. 使能CAN FD,设置CAN FD可变速率段位同步时间,将CAN FD可变速率段速度设置为5MHz,设置采样补偿SSP为自动计算模式。

  5. 使能CAN RX DMA功能。

  6. 使能CAN,配置CAN中断。

  7. 等待CAN总线打开。

备注

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

RCC_PeriphClockCmd(APBPeriph_CAN, APBPeriph_CAN_CLOCK, ENABLE);
...
init_struct.CAN_RxDmaEn = ENABLE;
CAN_Init(&init_struct);
CAN_Cmd(ENABLE);
CAN_INTConfig((CAN_BUS_OFF_INT | CAN_WAKE_UP_INT | CAN_ERROR_INT |
               CAN_RX_INT | CAN_TX_INT), ENABLE);
...
while (CAN_GetBusState() != CAN_BUS_STATE_ON)
...

rx_gdma_driver_init 包含了对GDMA RX外设的初始化。

  1. 使能PCC时钟。

  2. 使用GDMA通道0。

  3. 传输方向为 外设内存 传输。

  4. 设定源端地址固定,目的端地址递增。

  5. 设置源端地址为 CAN_DMA_RX_DATA ,目的端地址为0,后续动态调整。

  6. 设置源端handshake为 GDMA_Handshake_CAN_BUS_RX

  7. 使能GDMA总传输完成中断 GDMA_INT_Transfer

RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);
...
GDMA_InitStruct.GDMA_ChannelNum          = RX_GDMA_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_PeripheralToMemory;
GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Fix;
GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Inc;
...
GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)CAN_DMA_RX_DATA;
GDMA_InitStruct.GDMA_DestinationAddr     = 0;
GDMA_InitStruct.GDMA_SourceHandshake     = GDMA_Handshake_CAN_BUS_RX;
...
GDMA_INTConfig(RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...

tx_gdma_driver_init 包含了对GDMA TX外设的初始化。

  1. 使能PCC时钟。

  2. 使用GDMA通道1。

  3. 传输方向为 内存内存 传输。

  4. 设定源端地址递增,目的端地址递增。

  5. 源端地址为0,后续动态调整,目的端地址为 CAN_RAM

  6. 使能GDMA总传输完成中断 GDMA_INT_Transfer

RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);
...
GDMA_InitStruct.GDMA_ChannelNum          = TX_GDMA_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_MemoryToMemory;
GDMA_InitStruct.GDMA_BufferSize          = sizeof(CAN_RAM_TypeDef);
GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Inc;
GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Inc;
...
GDMA_InitStruct.GDMA_SourceAddr          = 0;
GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)CAN_RAM;
...
GDMA_INTConfig(TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...

功能实现

  1. 执行 can_dma_tx 函数,将tx_can_ram_struct设置为需要通过Tx DMA配置到RAM寄存器的值。

  2. 执行 can_start_tx_dma 函数。

    1. GDMA_SourceAddr 设置为tx_can_ram_struct的地址。

    2. 使能Tx DMA 的 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);
}
  1. 当Tx完成时,执行中断处理函数 TX_GDMA_Channel_Handler

    1. 失能Tx DMA的GDMA_INT_Transfer中断。

    2. 打印TX完成的log。

    3. 清除Tx DMA的 GDMA_INT_Transfer 中断标志位。

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);
  1. 执行 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)
...
  1. 当message buffer接收完成时,执行中断处理函数 CAN_Handler

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

    2. 调用 can_start_rx_dma 函数使能Rx DMA。

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);
}
  1. 执行 can_start_rx_dma 函数。

    1. 设置Rx DMA的buffer size和destination address。

    2. 使能Rx DMA的GDMA_INT_Transfer中断,使能Rx DMA。

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);
  1. 当Rx DMA接收完成时,执行中断处理函数 RX_GDMA_Channel_Handler

    1. 失能Rx DMA的 GDMA_INT_Transfer 中断。

    2. 使能message buffer的Rx DMA开始下一次接收。

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

    4. 清除Rx DMA的 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);