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]);
        }
    }
}

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