• Stm32使用串口空闲中断,基于队列来接收不定长、不定时数据


            串口持续地接收不定长、不定时的数据,把每一帧数据缓存下来且灵活地利用内存空间,下面提供一种方式供参考。原理是利用串口空闲中断和DMA,每当对方发来一帧完整的数据后,串口接收开始空闲,触发中断,在中断处理中新建一个接收队列节点,把DMA缓存的数据copy到接收队列里。当需要的时候就从接收队列里提出数据。定期清理队列防止堆空间溢出。

            话不多说,上代码。

    定义数据结构:

    /*USART接收队列*/
    
    typedef struct _USART_REC_Queue
    
    {
    
        u16 index;                          //序号
    
        char *buf;                          //链接的字符串
    
        struct _USART_REC_Queue*  next;     //链接到下一个节点
    
    }USART_REC_Queue;

    声明全局变量:

    #define USART3_REC_len 320                       //单次最大接收数
    
    extern u8 USART3_REC_buf[USART3_REC_len];        //用于DMA的临时数据中转
    
     
    
    extern u16 USART3_REC_counter;                   //接收计数器
    
    
    
    extern USART_REC_Queue* USART3_REC_Queue_head;   //接收队列固定头节点
    
    extern USART_REC_Queue* USART3_REC_Queue_tail;   //始终指向最后一个节点

    准备阶段:

    在启动汇编文件里,把堆空间改大,防止接收一点点数据就内存溢出。

    Heap_Size       EQU     0x00004000    //默认200字节,改大

    实例化全局变量:

    u8 USART3_REC_buf[320] = {0}; 
    
    u16 USART3_REC_counter = 0;
     
    
    USART_REC_Queue* USART3_REC_Queue_head = NULL;
    
    USART_REC_Queue* USART3_REC_Queue_tail = NULL;

    初始化各个硬件,使能了串口接收空闲中断,串口接收DMA,为接收队列头节点分配内存空间:

    void USART3_Init(u32 BaudRate)
    
    {
    
        //初始化参数结构体
    
        GPIO_InitTypeDef   GPIO_InitStruct;       //IO
    
        USART_InitTypeDef  USART_InitStruct;      //串口
    
        NVIC_InitTypeDef   NVIC_InitStruct;       //中断控制
    
        DMA_InitTypeDef    DMA_InitStruct;        //DMA
    
       
    
        /*全局指针初始化*/
    
        USART3_REC_Queue_head = USART_REC_Queue_Creat();  //构建串口3接收队列头节点
    
        USART3_REC_Queue_tail = USART3_REC_Queue_head;    //构建串口3接收队列尾节点
    
       
    
        //RCC使能
    
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);     //IO时钟
    
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);    //串口3时钟
    
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        //DMA时钟
    
       
    
        //PB11 USART1_TXD
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
    
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    
        GPIO_Init(GPIOB, &GPIO_InitStruct);  
    
        //PB10 USART1_RXD
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
    
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    
        //GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    
        GPIO_Init(GPIOB, &GPIO_InitStruct);
    
       
    
        //内嵌向量中断控制器初始化
    
        NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;
    
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级1
    
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;//子优先级1
    
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//使能IRQ通道
    
        NVIC_Init(&NVIC_InitStruct);
    
       
    
        //USART初始化
    
        USART_InitStruct.USART_BaudRate = BaudRate;//波特率 一般9600
    
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;//字节数据格式8位
    
        USART_InitStruct.USART_StopBits = USART_StopBits_1;//一个停止位
    
        USART_InitStruct.USART_Parity = USART_Parity_No;//无奇偶字节校验
    
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件流控制
    
        USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
    
        USART_Init(USART3, &USART_InitStruct);//初始化USART
    
        //USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//使能接收中断
    
        USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);//使能总线空闲中断
    
        USART_Cmd(USART3, ENABLE);//使能串口 
    
     
    
        DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR); //读取哪一个寄存器
    
        DMA_InitStruct.DMA_MemoryBaseAddr = (u32)(&USART3_REC_buf);      //读取到的数据的存放地址
    
        DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                  //指定外设为源地址
    
        DMA_InitStruct.DMA_BufferSize = USART3_REC_len;                  //数据存放区大小
    
        DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;    //外设寄存器地址是否偏移
    
        DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;             //数据存放地址是否偏移
    
        DMA_InitStruct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; //外设数据宽度8位
    
        DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;     //定义存储器数据宽度8位
    
        DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                       //正常操作模式
    
        DMA_InitStruct.DMA_Priority = DMA_Priority_High;                 //通道优先级
    
        DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                        //是否开启存储器到存储器模式
    
        DMA_Init(DMA1_Channel3, &DMA_InitStruct);                        //写入设置到DMA1通道  
    
        DMA_Cmd(DMA1_Channel3, ENABLE);                                  //使能DMA1通道 
    
        USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE);                   //注意不要忘了使能串口的DMA功能
    
    }

    串口中断处理(核心):

    void USART3_IRQHandler(void)
    
    { 
    
        if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
    
        {
    
            char *buf_new;                  //新字符串
    
            USART_REC_Queue* queue_new;     //新队列节点               
    
            u16 len;
    
           
    
            USART3->DR;                                       //读取数据。注意:这句必须要,否则不能够清除中断标志位
    
            USART_ClearITPendingBit(USART3, USART_IT_IDLE);                  //清中断
    
            DMA_Cmd(DMA1_Channel3, DISABLE);                                 //关闭DMA1通道3
    
           
    
            len = USART3_REC_len - DMA_GetCurrDataCounter(DMA1_Channel3);    //计算接收长度
    
            buf_new = (char *)malloc((len+5) * sizeof(char));                //为新字符串分配内存,预留空间添加序号
    
            //if(buf_new == NULL)  GPIO_SetBits(LedPort, Led1);              //内存不够的提示
    
            queue_new = USART_REC_Queue_Creat();                             //为新队列节点分配内存
    
            //if(queue_new == NULL)  GPIO_SetBits(LedPort, Led2);            //内存不够的提示
    
            USART3_REC_counter ++;                                           //计数器加1
    
            queue_new->index = USART3_REC_counter;                           //新节点的序号
    
            sprintf(buf_new, "#%d:%s", USART3_REC_counter, USART3_REC_buf);  //复制缓存到新字符串并添加序号
    
            queue_new->buf = buf_new;                                        //新队列节点链接新字符串
    
            USART3_REC_Queue_tail->next = queue_new;                         //接收队列尾节点链接新的节点
    
            USART3_REC_Queue_tail = queue_new;                               //更新尾节点
    
            
    
            DMA1_Channel3->CNDTR = USART3_REC_len;                           //重置DMA1通道3缓存计数器
    
            DMA_Cmd(DMA1_Channel3, ENABLE);                                  //重开DMA1通道3
    
        }
    
    }

    创建与销毁接收队列节点:

    USART_REC_Queue* USART_REC_Queue_Creat(void)
    
    {
    
        USART_REC_Queue* p_temp = (USART_REC_Queue*)malloc(sizeof(USART_REC_Queue));
    
        if(p_temp == NULL)   return NULL;   
    
        memset(p_temp, 0, sizeof(USART_REC_Queue));
    
        //p_temp->next = NULL;
    
        return p_temp; 
    
    }
    
     
    
    void USART_REC_Queue_Delete(USART_TypeDef* USARTx)
    
    {
    
        if(USARTx == USART3)
    
        {
    
            USART_REC_Queue* temp;
    
     
    
            temp = USART3_REC_Queue_head->next;
    
            if(temp == NULL || temp->next == NULL)   return;
    
            free(temp->buf);
    
            USART3_REC_Queue_head->next = temp->next;
    
            free(temp);
    
        } 
    
    }

    主函数里每隔5秒刷新显示接收队列的数据,并清理。

    while(1)
    
    {                        
    
        if(tim3_flag == 1)
        {
            tim3_flag = 0;
    
            USART_REC_Queue_display(USART3);
    
            USART_REC_Queue_Delete(USART3);
        }
    
    }

    在OLED屏上显示接收队列里的数据:

    void USART_REC_Queue_display(USART_TypeDef* USARTx)
    
    {
    
        if(USARTx == USART3)
    
        {
    
            USART_REC_Queue* temp = USART3_REC_Queue_head->next;
    
            if(temp == NULL)   return;                                //队列还没生成则返回
    
            OLED_Clear();                                             //清屏
    
            OLED_ShowString(0, 0, USART3_REC_Queue_head->next->buf);  //显示字符串
    
        }
    
    }
  • 相关阅读:
    HTML5
    9.13 开课第十天(JS脚本语音:语句:循环)
    php函数
    php基础语法
    mysql常用函数整理
    数据库经典练习题整理
    数据库练习小结
    数据库:高级查询
    CRUD操作
    SQL语句
  • 原文地址:https://www.cnblogs.com/ycf-studio/p/12061366.html
Copyright © 2020-2023  润新知