RT1052串口DMA接收不定长数据——2021.07

RT1052虽然外设架构与STM32完全不同,但是对于串口接收不定长数据来说,思路其实都是一样的,都是利用串口的空闲中断+DMA来实现。当然不定长数据的接收肯定不止这一种方法,这里只记录其中一种。

由于串口空闲中断+DMA接收不定长数据原理的介绍在网络上已经非常多了,这里不打算讲解原理,只介绍此方法在RT1052中的实现过程。

主体思路

  1. 初始化串口,使能串口空闲中断,按需求配置空闲检测位置和长度
  2. 配置DMA,使能串口DMA接收,如果需要DMA发送则使能DMA发送
  3. 启动DMA传输,等待串口接收数据
  4. 触发空闲中断后,在中断服务函数中记录已接收字节数并关闭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以确保数据一致性,接收后刷新和发送前刷新。

参考资料

  1. [野火]《i.MX RT库开发实战指南》
  2. IMXRT1050RM
  3. 【飞凌RT1052】串口空闲中断+接收DMA实现不定长接收详解
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值