STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发

1. CubeMX配置

        1.1 串口基本参数配置

         我这里选用串口一为例来实现,在cubeMX中启用串口1,参数保持默认即可。

STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发_第1张图片

1.2 启用全局中断

        在NVIC选项卡中勾选启用全局中断。STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发_第2张图片

1.3 启用串口接收DMA

         在DMA设置选项卡中点击Add按钮添加 USART1_RX ,并在模式设置中选择 Cirular 环形模式,其余参数不变,即启用加串口1的接收DMA功能,并实现环形缓冲。

STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发_第3张图片

 1.4 设置cubeMX工程外设使用的库为LL库

         在Project Manager选项卡(1)中的高级设置(2)中可以设置每个外设所使用的库,我们将所有外设的库全部选择为LL库。STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发_第4张图片

 2 生成工程修改

        2.1 建立接收缓冲区

        创建一个 UART_DAT 串口数据结构体,存放收发数据缓存指针,收发数据长度,收发标志位。

/* 串口设备数据接收结构体 */
typedef struct
{
    uint8_t  *pTxBuf;           /* 发送缓冲区 */
    uint8_t  *pRxBuf;           /* 接收缓冲区 */
    uint16_t LEN;               /* 接收到的数据长度 */
    uint8_t  FLG;               /* 接收标志位 */
}UART_DAT;

         开辟两个缓存区域用于串口数据的收发,通过 UART1_RX_BUF_SIZE和UART1_TX_BUF_SIZE控制缓冲区大小。

/* 定义串口缓冲区大小,分为发送缓冲区和接收缓冲区 */
#define UART1_TX_BUF_SIZE	1
#define UART1_RX_BUF_SIZE	1*512

/* 定义每个串口结构体变量 */
UART_DAT dat_Uart1 = {0};
static uint8_t g_TxBuf1[UART1_TX_BUF_SIZE+1];		/* 发送缓冲区 */
static uint8_t g_RxBuf1[UART1_RX_BUF_SIZE+1];		/* 接收缓冲区 */

         初始化串口结构体,设置串口收发缓存指针。

static void UartVarInit(void)
{
    /* 清空结构体 */
    memset(dat_Uart1, 0, sizeof(UART_DAT));

    /* 串口1 用到的缓存 */
    dat_Uart1.pRxBuf = g_RxBuf1;
    dat_Uart1.pTxBuf = g_TxBuf1;
}

        2.2 DMA初始化

        cubeMX生成的初始化中并不包含DMA的中断处理,也不包含DMA的开启,所以我们这里需要手动配置一下。

        先对DMA的接收缓存地址和长度进行配置,串口1的接收DMA对应为DMA1的通道5,DMA方向为外设(串口)到内存。所以我们这里先设置DMA的外设地址为串口1的地址,内存地址设置为串口接收缓冲区,并将缓冲区长度设置为 UART1_RX_BUF_SIZE 的大小,代码如下。

/* 配置外设地址 */
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_5, LL_USART_DMA_GetRegAddr(USART1));
/* 配置缓冲区地址 */
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_5, (uint32_t)dat_Uart1.pRxBuf);
/* 配置缓冲区大小 */
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, UART1_RX_BUF_SIZE);

        这里也可以使用函数 LL_DMA_ConfigAddresses 完成缓冲区和外设地址的设置。一个函数代替两个函数,不过参数比较多,不如上面整洁。

/* 配置地址 */
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_5,
                         LL_USART_DMA_GetRegAddr(USART1),
                         (uint32_t)dat_Uart1.pRxBuf,
                         LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5));

/* 设置缓冲区大小 */
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, UART1_RX_BUF_SIZE);

        2.3 DMA和串口中断启用        

        接下来我们在这里分别启用串口空闲中断、DMA传输完成中断、DMA传输错误中断。

    // 启用串口空闲中断
    LL_USART_EnableIT_IDLE(USART1);
    
    /* 开启DMA传输完成 传输错误中断 */
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_5);
    LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5);

        配置完成后启动DMA通道,并开启串口DMA接收功能。

    /* 启动串口接收DMA通道  并启用串口接收DMA */
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
    LL_USART_EnableDMAReq_RX(USART1);

        2.4 完整初始化函数

/*
*******************************************************************************************
*	函 数 名: InitHardUart
*	功能说明: 配置串口的硬件参数、启用串口中断等功能
*	形    参: 无
*	返 回 值: 无
*******************************************************************************************
*/
static void InitHardUart(void)
{
    /* 设置DMA接收缓存和缓存大小 */
    LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_5, LL_USART_DMA_GetRegAddr(USART1));
    LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_5, (uint32_t)dat_Uart1.pRxBuf);
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, UART1_RX_BUF_SIZE);
    
    
    // 启用串口空闲中断
    LL_USART_EnableIT_IDLE(USART1);
    
    /* 开启DMA传输完成 传输错误中断 */
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_5);
    LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5);
    
    /* 启动串口接收DMA通道  并启用串口接收DMA */
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
    LL_USART_EnableDMAReq_RX(USART1);
}

        2.5 串口中断函数

        我们通过 LL_USART_IsActiveFlag_IDLE() 函数和 LL_USART_IsEnabledIT_IDLE() 函数来判断串口是否进入空闲中断。

        在中断函数内部先清除空闲中断标志位并关闭DMA防止DMA继续接收数据,同时我们使用LL_DMA_GetDataLength() 函数来获取缓存区剩余空间大小,用缓冲区大小和剩余空间大小相减即可获取当前接收到的数据长度。

dat_Uart1.LEN = UART1_RX_BUF_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_5);

         这里将末尾置为0对字符串类型的数据进行截断,方便printf类型函数%s参数的打印。

dat_Uart1.pRxBuf[dat_Uart1.LEN] = 0;

        在计算完接收长度,并对数据处理后,我们重新设置DMA数据空间大小,并打开DMA接续下一帧数据的接收。

/*
*****************************************************************************************
*	函 数 名: MY_UART_IRQHandler
*	功能说明: 串口中断处理函数
*			  可同时处理多个串口的中断事件
*	形    参:USARTx: 串口设备
*	返 回 值: 无
*****************************************************************************************
*/
void MY_UART_IRQHandler(USART_TypeDef *USARTx)
{
      /* 判断空闲中断标志位 */
      if (LL_USART_IsActiveFlag_IDLE(USARTx) && LL_USART_IsEnabledIT_IDLE(USARTx))
      {
        // 清除空闲中断标志位
        LL_USART_ClearFlag_IDLE(USARTx);
        LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);
        
        /* LED灯翻转指示数据收发 */
        LL_GPIO_TogglePin(GPIOC, LEDB_Pin);
          
        dat_Uart1.LEN = UART1_RX_BUF_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_5);
          
        /* 打开接收完成标志位 并将数据传入接收函数中处理 */
        dat_Uart1.FLG = 1; 
        dat_Uart1.pRxBuf[dat_Uart1.LEN] = 0;
        
        /* 重新设置数据长度并打开DMA,使DMA从头开始接收 */
        LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, UART1_RX_BUF_SIZE);
        LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
      }
      else
      {
        /* 错误回调函数 */
        UART_Error_Callback();
      }
}

        最后我们将 MY_UART_IRQHandler() 函数放在CubeMX生成的中断函数中。

STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发_第5张图片

        2.6 DMA中断函数

        DMA传输出错的回调函数和中断如果不需要可以不启用,函数如下。

/*
*******************************************************************************************
*	函 数 名: DMA_ReceiveComplete_Callback
*	功能说明: DMA接收完成中断回调函数
*	形    参:无
*	返 回 值: 无
*******************************************************************************************
*/
void DMA_ReceiveComplete_Callback(void)
{
    /* 将接收满的数据打印 */
    LOG_RTT_INFO("%s", dat_Uart1.pRxBuf);
    
    /* 打印DMA缓冲满 */
    LOG_RTT_INFO("DMA data full !");
}

/*
*******************************************************************************************
*	函 数 名: DMA_TransferError_Callback
*	功能说明: DMA传输错误回调函数
*	形    参:无
*	返 回 值: 无
*******************************************************************************************
*/
void DMA_TransferError_Callback(void)
{
    /* 打印传输错误日志 */
    LOG_RTT_INFO("DMA data transfer failed !");
}

        并将两个中断函数放在CubeMX生成的中断函数中,在中断函数中清除中断标志位后再进入回调函数中。

void DMA1_Channel5_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */

    if(LL_DMA_IsActiveFlag_TC5(DMA1))
    {
        LL_DMA_ClearFlag_TC5(DMA1);
        DMA_ReceiveComplete_Callback();
    }
    else if(LL_DMA_IsActiveFlag_TE5(DMA1))
    {
        LL_DMA_ClearFlag_GI5(DMA1);
        DMA_TransferError_Callback();
    }
    
  /* USER CODE END DMA1_Channel5_IRQn 0 */

  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */
}

3 收发测试

        最后我们在主函数中添加如下代码测试收发,注意在操作标志位时需要关闭全局中断,操作完毕后再打开,防止出现中断和主函数同时写FLG变量。

if (dat_Uart1.FLG)
{
    /* 日志打印收到的数据 */ 
    LOG_RTT_INFO("Receive:%s", dat_Uart1.pRxBuf);

    /* 将收到的数据返回 */
    UartSend(dat_Uart1.pRxBuf, dat_Uart1.LEN);
    
    DISABLE_INT();
    dat_Uart1.FLG = 0;
    ENABLE_INT();
}

        下载到板子上,测试效果如下动图所示。

STM32单片机 Cubemx使用LL库 + 串口DMA + 空闲中断实现不定长高效数据收发_第6张图片

        如果对你有帮助记得点个赞点个关注。

你可能感兴趣的:(单片机,stm32,嵌入式硬件,c语言)