Transmit And Receive - DMA
CAN外接USB CAN FD分析仪,通过PC端收发标准数据帧,扩展数据帧,标准远程帧,扩展远程帧,FD标准数据帧,FD扩展数据帧等帧类型与CAN controller进行通信。
芯片端通过DMA接收PC下发的数据,并通过DMA传输相同数据给PC端,PC端收到芯片回的数据。
环境需求
该示例支持以下开发套件:
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
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下复位按键,开始运行。
测试验证
IC复位后,工具上会收到IC发送的数据帧。
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...
使用tool发送一个数据帧。
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...
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
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_init
、 can_driver_init
、 rx_gdma_driver_init
和 tx_gdma_driver_init
。
can_board_init
包含了对PAD和PINMUX的设置。
配置PAD:设置引脚、PINMUX模式、PowerOn、无内部上拉。
配置PINMUX:分配引脚分别为CAN_TX、CAN_RX功能。
can_driver_init
包含了对CAN外设的初始化。
使能PCC时钟。
关闭CAN的自动重传模式。
设置CAN总线同步位时间,将CAN总线速度设置为500kHz。
使能CAN FD,设置CAN FD可变速率段位同步时间,将CAN FD可变速率段速度设置为5MHz,设置采样补偿SSP为自动计算模式。
使能CAN RX DMA功能。
使能CAN,配置CAN中断。
等待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外设的初始化。
使能PCC时钟。
使用GDMA通道0。
传输方向为外设到内存传输。
设定源端地址固定,目的端地址递增。
设置源端地址为
CAN_DMA_RX_DATA
,目的端地址为0,后续动态调整。设置源端handshake为
GDMA_Handshake_CAN_BUS_RX
。使能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外设的初始化。
使能PCC时钟。
使用GDMA通道1。
传输方向为内存到内存传输。
设定源端地址递增,目的端地址递增。
源端地址为0,后续动态调整,目的端地址为
CAN_RAM
。使能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);
...
功能实现
执行
can_dma_tx
函数,将tx_can_ram_struct设置为需要通过Tx DMA配置到RAM寄存器的值。执行
can_start_tx_dma
函数。将
GDMA_SourceAddr
设置为tx_can_ram_struct的地址。使能Tx DMA 的
GDMA_INT_Transfer
中断。使能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);
}
当Tx完成时,执行中断处理函数
TX_GDMA_Channel_Handler
。失能Tx DMA的GDMA_INT_Transfer中断。
打印TX完成的log。
清除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);
执行
can_dma_rx
函数。屏蔽接收帧rtr,ide,id过滤,即接收所有帧。
使能Rx DMA,失能自动回复。
使用设置的接收帧类型将邮箱配置为接收模式。
使能邮箱接收中断,等待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)
...
当message buffer接收完成时,执行中断处理函数
CAN_Handler
。调用
CAN_GetRxDmaMsize()
函数获取Rx DMA buffer size。调用
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);
}
执行
can_start_rx_dma
函数。设置Rx DMA的buffer size和destination address。
使能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);
当Rx DMA接收完成时,执行中断处理函数
RX_GDMA_Channel_Handler
。失能Rx DMA的
GDMA_INT_Transfer
中断。使能message buffer的Rx DMA开始下一次接收。
打印接收到的帧ID,打印接收帧数据。
清除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);