stm32串口空闲中断+DMA传输接受不定长数据+letter shell 实现命令行

作用:

空闲中断(IDLE),俗称帧中断,即第一帧数据接收完毕到第二帧数据开始接收期间存在一个空闲状态(每接收一帧数据后空闲标志位置1),检测到此空闲状态后即执行中断程序。空闲中断的优点在于省去了帧头帧尾的检测,进入中断程序即意味着已经接收到一组完整数据,仅需及时对数据处理或将数据转移出缓冲区即可。

串口空闲中断在串口无数据接收的情况下,是不会产生的,产生的条件是当清除空闲标志位后,必须有接收到第一个数据后,才开始触发,一旦接收的数据断流,没有接收到数据,即产生空闲中断

简单说:

不用频繁进中断,省cpu力气

有些地方没写完,后续补上,里边操作系统是freertos,这个不是必须的

串口初始化

注意点:

一定要有串口电路,TTL转串口电路,串口接线正常

保证驱动CH340已安装

初始化完成清空串口缓冲区,保证无初始化乱码

初始化保证清空一下中断或者其他标志位,避免触发

中断优先级尽量低一点,避免影响其他业务

windows回车记住是\r\n

时钟和gpio保证初始化正确


#define DEBUG_USART                        USART3
#define DEBUG_USART_TX_AF                  GPIO_AF_USART3
#define DEBUG_USART_RX_AF                  GPIO_AF_USART3
#define DEBUG_USART_CLK_EN()               RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE)
#define DEBUG_USART_TX_CLK_EN()            RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE)        
#define DEBUG_USART_RX_CLK_EN()            RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE)
#define DEBUG_USART_IRQn                   USART3_IRQn
#define ENTER_PRI                          '\r'
#define IS_TIME_OUT(x) while((x)) {}
#define IRQ_DEBUG_IDE           (5)

/**
 * @description: debug 初始化结构体
 * @detail: 
 * @param {ULONG} ulBound
 * @return {*}
 * @author: lkc
 */
STATIC VOID Bsp_Debug_InitType(ULONG ulBound)
{
    DEBUG_USART_CLK_EN();
    DEBUG_USART_TX_CLK_EN();
    DEBUG_USART_RX_CLK_EN();

    USART_InitTypeDef USART_InitStructure;

    USART_InitStructure.USART_BaudRate = ulBound;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; 
    USART_Init(DEBUG_USART, &USART_InitStructure); 

    USART_Cmd(DEBUG_USART, ENABLE);

    USART_ClearFlag(DEBUG_USART, USART_FLAG_TC);

    /* 清空发送缓冲区 */
    while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TC) == RESET)
    {
        USART_ReceiveData(DEBUG_USART);
    }

    return;
}

/**
 * @description: debug 中断
 * @detail: 
 * @return {*}
 * @author: lkc
 */
STATIC VOID Bsp_Debug_Nvic(VOID)
{
    /* 空闲中断使用命令行 */
    NVIC_InitTypeDef NVIC_InitStre;
    NVIC_InitStre.NVIC_IRQChannel = DEBUG_USART_IRQn;
    NVIC_InitStre.NVIC_IRQChannelPreemptionPriority = IRQ_DEBUG_IDE;
    NVIC_InitStre.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_InitStre.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStre);

    /* 空闲中断,接收到一连串数据触发一次 */
    USART_ITConfig(DEBUG_USART, USART_IT_IDLE, ENABLE);
    USART_ClearITPendingBit(DEBUG_USART, USART_IT_IDLE);

    return;
}

/**
 * @Description: 串口初始化
 * @author: lkc
 * @Date: 2023-02-17 23:06:43
 * @param {int} baud
 * @return {*}
 */
VOID Bsp_Usart_DebugInit(ULONG ulBound)
{
    Bsp_Debug_InitType(ulBound);
    Bsp_Debug_Nvic();

    return;
}

gpio初始化

注意:

  1. pin和source不是一个东西

  1. source复用的时候不能和pin一样直接全部与|上复用


#define DEBUG_PORT              DEBUG_USART_TX_GPIO_PORT
#define DEBUG_PIN               DEBUG_USART_TX_PIN | DEBUG_USART_RX_PIN 
#define DEBUG_USART_TX_SOURCE              GPIO_PinSource10
#define DEBUG_USART_RX_SOURCE              GPIO_PinSource11
/**
 * @description: 输出模式 推挽
 * @detail: 
 * @param {GPIO_TypeDef*} GPIOx
 * @param {GPIO_InitTypeDef*} GPIO_InitStruct
 * @return {*}
 * @author: lkc
 */
VOID Bsp_Gpio_ModeAF(GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin, uint16_t GPIO_PinSource, uint8_t GPIO_AF)
{
    /* gpio时钟默认前边初始化 */

    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    /* 定时器复用使用浮空.其他复用未知 */
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOx, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOx, GPIO_PinSource, GPIO_AF);

    return;
}
/**
 * @description: 
 * @detail: 
 * @return {*}
 * @author: lkc
 */
VOID Bsp_Usart3_Gpio(VOID)
{
    // TODO 复用source不能| ,PIN 和 PIN SOURCE有区别,一个是引脚 一个是复用资源
    Bsp_Gpio_ModeAF(DEBUG_PORT, DEBUG_PIN, DEBUG_USART_TX_SOURCE, GPIO_AF_USART3);
    Bsp_Gpio_ModeAF(DEBUG_PORT, DEBUG_PIN, DEBUG_USART_RX_SOURCE, GPIO_AF_USART3);

    return;
}
/**
 * @description: GPIO初始化
 * @detail: 
 * @return {*}
 * @author: lkc
 */
void Bsp_Gpio_Init(void)
{
    /* 初始化时钟 */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

    /* 串口gpio */
    Bsp_Usart3_Gpio();

    return;
}

DMA初始化

注意:

USART_DMACmd一定要指定串口


/* 串口接收中断 */
UCHAR gucURxBuf[USART_RX_BUF_SIZE] = {0};
/**
 * @description: dma1 初始化
 * @detail: 
 * @return {*}
 * @author: lkc
 */
VOID Bsp_Dma1_Init(VOID)
{
    /* 每个dma数据流和通道对应不同的外设 */
    DMA_DeInit(DMA1_Stream1);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

    DMA_InitTypeDef DMA_InitStructure;
    while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE){} /* 检测dma是否之前配置过 */
    DMA_InitStructure.DMA_Channel = DMA_Channel_4; 
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DEBUG_USART->DR); /* 外设地址 */
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)gucURxBuf; /* 内存地址 */
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; /* 外设到内存 */
    DMA_InitStructure.DMA_BufferSize = (uint32_t)32; /* 一次性传输大小 */
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /* 外设地址不变 */
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* memory地址自增 */
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; /* 外设地址数据单位 */
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /* memory地址数据单位 */
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* 注意! 这里一定要使用循环模式,否则收到一包之后就停止了 */
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; /* 优先级 */
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;       
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; /* 存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。*/
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /* 外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。 */
    DMA_Init(DMA1_Stream1, &DMA_InitStructure); 

    return;
}

/**
 * @description: 串口接受dma
 * @detail: 
 * @return {*}
 * @author: lkc
 */
VOID Bsp_Dma_UsartRx(VOID)
{
    Bsp_Dma1_Init();

    DMA_SetCurrDataCounter(DMA1_Stream1, USART_RX_BUF_SIZE);
    DMA_Cmd(DMA1_Stream1, ENABLE);

    // TODO 注意 如果是使能dma一定要加这个
    USART_DMACmd(DEBUG_USART, USART_DMAReq_Rx, ENABLE);

    return;
}

串口中断

注意点:


/**
 * @description: 串口3中断
 * @detail: 
 * @return {*}
 * @author: lkc
 */
extern QueueHandle_t queueCmdHandle;
ULONG ulRxLen = 0;
VOID Bsp_Usart_Handler(VOID)
{
    if (RESET != USART_GetITStatus(DEBUG_USART, USART_IT_IDLE))
    {
        /* 个人认为是清空缓冲,取出数据 */
        DEBUG_USART->SR;
        DEBUG_USART->DR;

        /* 清空空闲中断 */
        USART_ClearITPendingBit(DEBUG_USART, USART_IT_IDLE);
        /* 关闭dma */
        DMA_Cmd(DMA1_Stream1, DISABLE);
        /* 本帧数据长度=DMA准备的接收数据长度-DMA已接收数据长度 */
        ulRxLen = USART_RX_BUF_SIZE - DMA_GetCurrDataCounter(DMA1_Stream1); 

        /* 此时知道接收的长度以及Buf,直接传一个队列 */
        osMessageQueuePut(queueCmdHandle, &ulRxLen, NULL, 0);
        DMA_ClearFlag(DMA1_Stream1, DMA_FLAG_TCIF4 | DMA_FLAG_FEIF4 | DMA_FLAG_DMEIF4 |
                DMA_FLAG_TEIF4 | DMA_FLAG_HTIF4);//清除DMA2_Steam7传输完成标志
        IS_TIME_OUT(DMA_GetCmdStatus(DMA1_Stream1) != DISABLE);
        
        DMA_SetCurrDataCounter(DMA1_Stream1, USART_RX_BUF_SIZE);
        DMA_Cmd(DMA1_Stream1, ENABLE);
    }
    
    return;
}

/**
 * @description: 串口3中断,用于命令行
 * @detail: 
 * @return {*}
 * @author: lkc
 */
void USART3_IRQHandler( void )
{
    Bsp_Usart_Handler();
    return;
}

处理传递字符串

注意点


/**
 * @description: 命令行任务
 * @detail: 
 * @return {*}
 * @author: lkc
 */
extern Shell shell;
void Cmd_Task(void *argument)
{
    PRINTF("Cmd Task Init\r\n");
    userShellInit();

    STATIC ULONG ulRxDataLen;
    ULONG i = 0;
    /* 创建队列用于接收DMA */
    queueCmdHandle = osMessageQueueNew(CMD_QUEUE_LENGTH, CMD_QUEUE_SIZE, NULL);

    while (1)
    {
        /* 等待队列 */
        osMessageQueueGet(queueCmdHandle, &ulRxDataLen, NULL, portMAX_DELAY);

        for (i = 0; i < ulRxDataLen; i++)
        {
            /* 这个地方为调用字符处理的地方 */
            shellHandler(&shell, gucURxBuf[i]);
        }
    }
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用DMA空闲中断接收不定数据的步骤如下: 1. 初始化串口DMA:配置串口为接收模式,启用DMA传输;配置DMA为循环模式,传输大小为1字节,传输方向为从串口接收数据到内存。 2. 开启DMA传输:调用DMA启动函数启动DMA传输。 3. 开启串口接收中断:调用串口中断使能函数,开启空闲中断。 4. 在空闲中断中处理数据:当DMA传输完成并且串口没有接收到新数据时,说明接收完成,可以在空闲中断中处理接收到的数据。 下面是一个简单的例子: ```c #include "stm32f4xx_hal.h" #define RX_BUF_SIZE 256 UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_rx; uint8_t rx_buf[RX_BUF_SIZE]; uint8_t rx_len = 0; uint8_t rx_flag = 0; void UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } hdma_usart1_rx.Instance = DMA2_Stream2; hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx); HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { rx_len += RX_BUF_SIZE - hdma_usart1_rx.Instance->NDTR; HAL_UART_Receive_DMA(huart, rx_buf, RX_BUF_SIZE); } } void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { rx_len += RX_BUF_SIZE / 2 - hdma_usart1_rx.Instance->NDTR; } } void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { __HAL_UART_CLEAR_PEFLAG(&huart1); HAL_UART_Receive_DMA(huart, rx_buf, RX_BUF_SIZE); } } void DMA2_Stream2_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart1_rx); } void HAL_UART_IdleCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { if (hdma_usart1_rx.Instance->NDTR == RX_BUF_SIZE) { rx_len = RX_BUF_SIZE; rx_flag = 1; } else { rx_len = RX_BUF_SIZE - hdma_usart1_rx.Instance->NDTR; } } } int main(void) { HAL_Init(); UART_Init(); while (1) { if (rx_flag) { // 处理接收到的数据 rx_flag = 0; } } } ``` 在上面的例子中,我们使用了循环DMA传输模式,当接收到一定数量的数据后,将触发空闲中断,并在空闲中断中处理接收到的数据。同时,在DMA传输完成和空闲中断中,我们使用了两个不同的回调函数,分别处理DMA传输完成和空闲中断的事件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值