RT1052虽然外设架构与STM32完全不同,但是对于串口接收不定长数据来说,思路其实都是一样的,都是利用串口的空闲中断+DMA来实现。当然不定长数据的接收肯定不止这一种方法,这里只记录其中一种。
由于串口空闲中断+DMA接收不定长数据原理的介绍在网络上已经非常多了,这里不打算讲解原理,只介绍此方法在RT1052中的实现过程。
主体思路
- 初始化串口,使能串口空闲中断,按需求配置空闲检测位置和长度
- 配置DMA,使能串口DMA接收,如果需要DMA发送则使能DMA发送
- 启动DMA传输,等待串口接收数据
- 触发空闲中断后,在中断服务函数中记录已接收字节数并关闭DMA传输
硬件平台
野火 i.MX RT1052 EVK Pro开发板
软件实现
- 配置串口
#define UART_RX_GPIO GPIO1
#define UART_RX_GPIO_PIN (19U)
#define UART_RX_IOMUXC IOMUXC_GPIO_AD_B1_03_LPUART2_RX
#define UART_TX_GPIO GPIO1
#define UART_TX_GPIO_PIN (18U)
#define UART_TX_IOMUXC IOMUXC_GPIO_AD_B1_02_LPUART2_TX
#define DEMO_LPUART LPUART2
#define DEMO_LPUART_CLK_FREQ BOARD_DebugConsoleSrcFreq()
/**
* @brief 串口GPIO初始化
* @param 无
* @retval 无
* @note 无
*/
void UART_GPIO_Init(void)
{
/* 定义gpio初始化配置结构体 */
gpio_pin_config_t gpio_config = {0};
/*GPIO配置*/
gpio_config.direction = kGPIO_DigitalInput; //输入模式
gpio_config.interruptMode = kGPIO_NoIntmode; //不使用中断
GPIO_PinInit(UART_RX_GPIO, UART_RX_GPIO_PIN, &gpio_config);
gpio_config.direction = kGPIO_DigitalOutput; //输出模式
gpio_config.outputLogic = 1; //默认高电平,在输入模式下配置该选项无效
GPIO_PinInit(UART_TX_GPIO, UART_TX_GPIO_PIN, &gpio_config);
IOMUXC_SetPinMux(UART_RX_IOMUXC, 0U);
IOMUXC_SetPinMux(UART_TX_IOMUXC, 0U);
IOMUXC_SetPinConfig(UART_RX_IOMUXC, UART_RX_PAD_CONFIG_DATA);
IOMUXC_SetPinConfig(UART_TX_IOMUXC, UART_TX_PAD_CONFIG_DATA);
}
/**
* @brief LPUART初始化
* @param 无
* @retval 无
* @note 无
*/
void UART_Init(void)
{
lpuart_config_t lpuartConfig;
/* 初始化UART引脚 */
UART_GPIO_Init();
/* 获取LPUART默认配置 */
LPUART_GetDefaultConfig(&lpuartConfig);
/* 在默认配置基础上继续配置参数 */
lpuartConfig.baudRate_Bps = 921600;
lpuartConfig.enableTx = true;
lpuartConfig.enableRx = true;
lpuartConfig.rxIdleConfig = kLPUART_IdleCharacter1;
lpuartConfig.rxIdleType = kLPUART_IdleTypeStopBit;
LPUART_Init(DEMO_LPUART, &lpuartConfig, DEMO_LPUART_CLK_FREQ);
/* 清除可能残留的标志位,并使能空闲中断 */
LPUART_ClearStatusFlags(DEMO_LPUART, kLPUART_IdleLineFlag);
LPUART_EnableInterrupts(DEMO_LPUART, kLPUART_IdleLineInterruptEnable);
/* 打开串口中断 */
EnableIRQ(LPUART2_IRQn);
}
配置串口参数的时候,rxIdleConfig和rxIdleType 我设置成了从停止位开始检测,连续检测到一个字符的空闲时触发空闲中断,不过这两个完全按照默认的设置也能实现功能,目前还不清楚为什么。
- 配置DMA
#define LPUART_TX_DMA_CHANNEL 0U //UART发送使用的DMA通道号
#define LPUART_RX_DMA_CHANNEL 1U //UART接收使用的DMA通道号
#define LPUART_TX_DMA_REQUEST kDmaRequestMuxLPUART2Tx //定义串口DMA发送请求源
#define LPUART_RX_DMA_REQUEST kDmaRequestMuxLPUART2Rx //定义串口DMA接收请求源
#define LPUART_DMAMUX_BASEADDR DMAMUX //定义所使用的DMA多路复用模块(DMAMUX)
#define LPUART_DMA_BASEADDR DMA0 //定义使用的DMA
#define ECHO_BUFFER_LENGTH 128 //UART接收和发送数据缓冲区长度
/* 收发缓冲区 */
SDK_L1DCACHE_ALIGN(uint8_t g_txBuffer[ECHO_BUFFER_LENGTH]) = {0};
SDK_L1DCACHE_ALIGN(uint8_t g_rxBuffer[ECHO_BUFFER_LENGTH]) = {0};
/*句柄定义*/
lpuart_edma_handle_t g_lpuartEdmaHandle; //串口DMA传输句柄
edma_handle_t g_lpuartTxEdmaHandle; //串口DMA发送句柄
edma_handle_t g_lpuartRxEdmaHandle; //串口DMA接收句柄
lpuart_transfer_t g_sendXfer; //定义发送传输结构体
lpuart_transfer_t g_receiveXfer; //定义接收传输结构体
/**
* @brief 串口DMA初始化
* @param 无
* @retval 无
* @note 无
*/
void UART_DMA_Init(void)
{
edma_config_t config;
/*初始化DMAMUX */
DMAMUX_Init(LPUART_DMAMUX_BASEADDR);
/* 为LPUART设置DMA传输通道 */
DMAMUX_SetSource(LPUART_DMAMUX_BASEADDR, LPUART_TX_DMA_CHANNEL, LPUART_TX_DMA_REQUEST);
DMAMUX_SetSource(LPUART_DMAMUX_BASEADDR, LPUART_RX_DMA_CHANNEL, LPUART_RX_DMA_REQUEST);
DMAMUX_EnableChannel(LPUART_DMAMUX_BASEADDR, LPUART_TX_DMA_CHANNEL);
DMAMUX_EnableChannel(LPUART_DMAMUX_BASEADDR, LPUART_RX_DMA_CHANNEL);
/* 初始化DMA */
EDMA_GetDefaultConfig(&config);
EDMA_Init(LPUART_DMA_BASEADDR, &config);
/* 创建eDMA传输句柄 */
EDMA_CreateHandle(&g_lpuartTxEdmaHandle, LPUART_DMA_BASEADDR, LPUART_TX_DMA_CHANNEL);
EDMA_CreateHandle(&g_lpuartRxEdmaHandle, LPUART_DMA_BASEADDR, LPUART_RX_DMA_CHANNEL);
/* 创建 LPUART DMA 句柄 */
LPUART_TransferCreateHandleEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, NULL, NULL, &g_lpuartTxEdmaHandle, &g_lpuartRxEdmaHandle);
/* 启动传输 */
g_receiveXfer.data = g_rxBuffer;
g_receiveXfer.dataSize = ECHO_BUFFER_LENGTH;
LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &g_receiveXfer);
}
DMA的收发缓冲我没有放在nonecache区,所以使用SDK_L1DCACHE_ALIGN做内存对齐,方便cache操作。
- 中断函数
/* 接收字节数计数 */
__IO uint32_t g_bufflength;
void LPUART2_IRQHandler(void)
{
/* 判断中断源 */
if(LPUART_GetStatusFlags(DEMO_LPUART) & kLPUART_IdleLineFlag)
{
/* 清除空闲中断标志位 */
LPUART_ClearStatusFlags(DEMO_LPUART, kLPUART_IdleLineFlag);
/* 刷新D-Cache,确保g_rxBuffer数据一致性 */
DCACHE_CleanInvalidateByRange((uint32_t)g_rxBuffer, ECHO_BUFFER_LENGTH);
/* 获取当前已接收字节数 */
LPUART_TransferGetReceiveCountEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, (uint32_t*)&g_bufflength);
/* 关闭DMA传输 */
LPUART_TransferAbortReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle);
/*
*数据处理
*/
/* 刷新D-Cache,确保g_txBuffer数据一致性 */
DCACHE_CleanInvalidateByRange((uint32_t)g_txBuffer, ECHO_BUFFER_LENGTH);
/* 回环测试,配置发送结构体 */
g_sendXfer.data = g_txBuffer;
g_sendXfer.dataSize = g_bufflength;
LPUART_SendEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &g_sendXfer);
/* 重新开始DMA接收传输 */
LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &receiveXfer);
}
}
中断函数里有个地方需要注意,一定要在关闭DMA传输前获取已接收的字节数,这点跟STM32完全不同,字节数计数变量最好用全局的,因为有可能外部处理函数需要用到。我这里为了做回环测试,把DMA发送也写在中断里了,实际项目应用的时候发送函数基本是放在其他地方的。
由于DMA收发缓冲没有定义在nonecache区,所以在开启D-Cache又使用DMA时,需要刷新Cache以确保数据一致性,接收后刷新和发送前刷新。
参考资料
- [野火]《i.MX RT库开发实战指南》
- IMXRT1050RM
- 【飞凌RT1052】串口空闲中断+接收DMA实现不定长接收详解