STM32单片机串口空闲中断接收不定长数据

发布者:Xiaohan521最新更新时间:2022-01-27 来源: eefocus关键字:STM32  单片机  不定长数据 手机看文章 扫描二维码
随时随地手机看文章

在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成。但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志,对于这样的数据通常的做法是每隔一段时间查看一下接收数据的长度是否发生了变化,如果指定的一段时间内接收数据长度没有发生变化,就认为是一帧数据发送完成。在STM32单片机中串口提供了一个更好用的功能,就是空闲中断功能。也就是说当一帧数据发送结束后,就会产生一个空闲中断。这样就可以利用这个空闲中断来判断一帧数据接收是否完成。


关于串口空闲检测可以在STM32参考手册上找到相关介绍

通过这个图可以看出来,当第一组数据Data1、Data2、Data3、Data4发送结束后,总线就会处于空闲状态,这时就会产生一个空闲中断。


当Data1、Data2、Data3、Data4每一个数据到来时串口产生的中断为 RXNE:读数据寄存器非空中断。具体的相关介绍可以在状态寄存器(USART_SR)中查看。

也就是每接收一个字节,串口会产生一个RXNE中断,当一帧数据发送完成就产生一个IDLE中断。


这样就可以在串口每个RXNE中断来临后将数据先存储起来,然后在IDLE中断到来后说明数据接收结束。这时候就可以去处理接收到的数据了。


下面就通过代码来说明一下如何使用串口空闲中断来接收不定长数据


首先初始化串口


void  uart2_init( u16 baud )

{

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    NVIC_InitTypeDef  NVIC_InitStructure;

 

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

    RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

 

    NVIC_Init( &NVIC_InitStructure );

 

    USART_InitStructure.USART_BaudRate = baud;

    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    USART_InitStructure.USART_Parity = USART_Parity_No;

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

 

    USART_Init( USART2, &USART_InitStructure );

 

    USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断

    USART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断

    USART_Cmd( USART2, ENABLE ); //使能串口2

}

与常规的串口初始化唯一不同的就是多了一行空闲中断初始化代码


USART_ITConfig( USART2, USART_IT_IDLE, ENABLE );                                             //使能串口空闲中断


下来在中断中根据中断类型来处理数据


void USART2_IRQHandler( void )

{

    u8 tem = 0;

 

    if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET )   //接收中断  接收到一个字节产生一次中断

    {

        tem = USART_ReceiveData( USART2 ); //读取数据,可以自动将中断标志位RXNE清零

        rec_buff[uart2_rec_cnt++] = tem; //存储接收到的数据

    }

    if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET )//空闲中断 接收到一帧数据 产生一次中断

    {

        tem = USART2->SR; //读取SR寄存器

        tem = USART2->DR;    //读取DR寄存器 (先读USART_SR,然后读USART_DR可以清除空闲中断标志位IDLE)

       

        copy_data( rec_buff, uart2_rec_cnt ); //备份数据

        receiveOK_flag = 1; //接收完成标志位置位

        uart2_rec_cnt = 0;                     //接收数据长度清零

    }

}

        当串口是RXNE中断时,就直接将数据存储到数组中,当IDLE中断到来后就将接收到的数据存储起来,然后置位接收一帧数据成功标志。这样主程序在检测到接收数据成功标志后,就可以去处理数据了。


        这样通过IDLE中断,就可以轻松接收到不定长的数据了,不论数据长度是多少,只要是一帧数据结束,IDLE中断就可以检测到。


 完整代码如下


#ifndef __UART2_H

#define __UART2_H

#include "sys.h"

 

#define   UART2_REC_LEN   20 //串口缓存区长度

 

void  uart2_init( u16 baud );

void uartDMA_Init( void );

void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx );

void uart2_Send( u8 *buf, u16 len );

void copy_data( u8 *buf, u16 len );

 

#endif

//串口2空闲中断,接收不定长数据

#include "uart2.h"

u8 rec_buff[UART2_REC_LEN] = {0};

u16 uart2_rec_cnt = 0;             //串口接收数据长度

u8 data_backup[UART2_REC_LEN] = {0}; //数据备份

u16 dataLen_backup = 0; //长度备份

_Bool receiveOK_flag = 0; //接收完成标志位

/*

空闲中断是什么意思呢?

指的是当总线接收数据时,一旦数据流断了,此时总线没有接收传输,处于空闲状态,IDLE就会置1,产生空闲中断;又有数据发送时,IDLE位就会置0;

注意:置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。

所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。

*/

 

void  uart2_init( u16 baud )

{

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    NVIC_InitTypeDef  NVIC_InitStructure;

 

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

    RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );

 

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

 

    NVIC_Init( &NVIC_InitStructure );

 

    USART_InitStructure.USART_BaudRate = baud;

    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    USART_InitStructure.USART_Parity = USART_Parity_No;

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

 

    USART_Init( USART2, &USART_InitStructure );

 

    USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断

    USART_ITConfig( USART2, USART_IT_RXNE, ENABLE );   //使能串口RXNE接收中断

    USART_Cmd( USART2, ENABLE ); //使能串口2

}

 

//发送len个字节

//buf:发送区首地址

//len:发送的字节数

void uart2_Send( u8 *buf, u16 len )

{

    u16 t;

    for( t = 0; t < len; t++ ) //循环发送数据

    {

        while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );

        USART_SendData( USART2, buf[t] );

    }

    while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );

}

 

//备份接收到的数据

void copy_data( u8 *buf, u16 len )

{

    u16 t;

    dataLen_backup = len; //保存数据长度

    for( t = 0; t < len; t++ )

    {

        data_backup[t] = buf[t]; //备份接收到的数据,防止在处理数据过程中接收到新数据,将旧数据覆盖掉。

    }

}

 

//利用空闲中断接收串口不定长数据

//RXNE中断和IDLE中断的区别?

//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。

 

void USART2_IRQHandler( void )

{

    u8 tem = 0;

 

    if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET )    //接收中断  接收到一个字节产生一次中断

    {

        tem = USART_ReceiveData( USART2 ); //读取数据,可以自动将中断标志位RXNE清零

        rec_buff[uart2_rec_cnt++] = tem;      //存储接收到的数据

    }

    if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET ) //空闲中断 接收到一帧数据 产生一次中断

    {

        tem = USART2->SR; //读取SR寄存器

        tem = USART2->DR; //读取DR寄存器 (先读USART_SR,然后读USART_DR可以清除空闲中断标志位IDLE)

        //uart2_Send( rec_buff, uart2_rec_cnt ); //一帧数据接收完毕,将接收到的数据发送出去

        copy_data( rec_buff, uart2_rec_cnt );    //备份数据

        receiveOK_flag = 1; //接收完成标志位置位

        uart2_rec_cnt = 0; //接收数据长度清零

    }

}

 

#include "sys.h"

#include "delay.h"

#include "usart.h"

#include "led.h"

#include "key.h"

#include "uart2.h"

#include "string.h"

 

extern u8 data_backup[UART2_REC_LEN]; //数据备份

extern u16 dataLen_backup; //长度备份

extern _Bool receiveOK_flag; //接收完成标志位

 

 

int main( void )

{

    u8  j = 0;

 

    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );

 

    delay_init();        //延时函数初始化

    LED_Init();          //初始化与LED连接的硬件接口

    uart2_init( 9600 );

    while( 1 )

    {

        if( receiveOK_flag ) //一帧数据接收完成后开始处理数据

        {

            receiveOK_flag = 0;

            uart2_Send( data_backup, dataLen_backup ); //发送数据

            memset( data_backup, 0, sizeof( data_backup ) ); //清空备份数组

        }

 

        j++;

        if( j > 50 )

        {

            j = 0;

            LED = !LED;

[1] [2]
关键字:STM32  单片机  不定长数据 引用地址:STM32单片机串口空闲中断接收不定长数据

上一篇:STM32单片机串口空闲中断+DMA接收不定长数据
下一篇:STM32F103单片机串口通信带奇偶校验位

推荐阅读最新更新时间:2024-11-09 13:42

STM32入门系列-STM32时钟系统,STM32时钟树
时钟对于单片机来说是非常重要的,它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行。时钟系统犹如人的心脏,一旦有问题整个系统就崩溃。我们知道STM32属于高级单片机,其内部有很多的外设,但不是所有外设都使用同一时钟频率工作,比如内部看门狗和RTC,它只需30KHz的时钟频率即可工作,所以内部时钟源就有多种选择。在前面章节的介绍中,我们知道STM32系统复位后首先进入SystemInit函数进行时钟的设置,将STM32F1系统时钟设置为72MHz,然后进入主函数。那么这个系统时钟大小如何得来,其他外设的时钟又如何划分,可以通过一张时钟树图找到答案,只要理解好时钟树,STM32一切时钟的来龙去脉就会非常清楚。下面就来了解下时钟
[单片机]
如何使用STM32通用Bootloader让OTA更加Easy
随着物联网时代到来,越来越多的智能设备拥有了在线升级的能力,无论是系统更新,产品功能迭代还是漏洞修复都能在第一时间抵达用户手中的智能设备。 在线升级功能需要使用 OTA (Over-the-Air) 技术 ,OTA 技术简单地说就是通过网络来升级手中的智能设备,进而使用设备最新版本的功能。而 OTA 技术中必不可少的一环就是通过 Bootloader 来管理、升级设备固件。 由于 OTA 功能由多种复杂技术组成,因此添加 OTA 功能有一定的技术门槛。很多开发者遇到莫名其妙的问题,进而导致 OTA 升级失败,常见问题如下: 升级过程缺少专业的安全机制 APP 无法正常启动 程序莫名跑飞,出现 hardfault 固件搬运失
[单片机]
如何使用<font color='red'>STM32</font>通用Bootloader让OTA更加Easy
基于STM32的智能加油系统设计方案
毕设介绍 针对当前汽车加油需求的日益增多,基于STM32开发技术和传感器技术开发一款智能加油系统。 题目要求 智能加油系统应具备油量控制、根据油费计价、加油环境检测的功能。该系统能模拟汽车加油的全过程,并具备远程控制功能。 题目分析 本次毕业设计是智能加油系统的设计与实现,设计所包含的模块主要有oled显示电路、电磁阀驱动电路、可燃气体传感器模块、ds18b20数字温度传感器、蜂鸣器报警模块、WIFI模块、按键电路模块及电源模块。 通过模块之间的配合实现智能加油系统,一旦有温度异常或可燃气体浓度超标将会通过wifi模块对管理人员发出警告。 stm32f103c8t6实时采集ds18b20温度传感器获取温度,如果温度
[单片机]
基于<font color='red'>STM32</font>的智能加油系统设计方案
STM32定时器(一)PWM输出
一、 STM32定时器分类众多,按照内核、外核标准分为两部分:核内定时器+核外定时器 1)核内定时器:Systick 2)外设定时器:特定应用定时器+常规定时器 3)特定应用定时器:LPTIM,RTC,WTD,HRTIM 4)常规定时器:基本定时器TIM6&TIM7)、通用定时器(TIM2TIM5,TIM9TIM14)、高级定时器(TIM1&TIM8) 【常规定时器: 基本定时器:计划没有任何对外输入/输出,常用作时基,实现基本的计数和定时功能。 通用定时器:除了基本的定时器的时基功能外,还可以对外作输入捕获、输出比较以及连接其他传感器接口(除了编码器和霍尔传感器) 高级定时器:此类的定时器功能强大,除了具备通用的定时器的功
[单片机]
<font color='red'>STM32</font>定时器(一)PWM输出
对寄存器操作方法的经验和其总结
接触了一阵子的STM32函数库,使用起来挺方便的,但是很少有处理器会有函数库,大部分情况下还是要自己来对寄存器进行操作,所以还是不要生疏了对寄存器的操作。 对寄存器的操作有时候要考虑对其不同的位进行先后顺序不同的设置,因为这样可能达不到预期的效果,这个不太好总结,但是对寄存器操作的方法是固定的。 在这之前,首先要明白逻辑运算符(! && ||)和位运算符( ~ | ^&)的区别,对寄存器的操作使用的是位运算符,逻辑运算符一般用于在程序中判断逻辑中使用。 例如 定义一个8位的寄存器(0xf0是寄存器的地址): #defineREG0xFF 1)对单个的位进行赋值 (1)将寄存器REG的第5位置“1” REG|=(1
[单片机]
对寄存器操作方法的经验和其总结
STM32】1—零基础硬件软件配置 & 完成LED的闪烁
1 基础准备 1.1 硬件准备 首先,我们需要用四根杜邦线完成开发板与下载器的连接。 连接方式:DAPLINK上的3V3、SWD、SCK、GND,分别通过杜邦线与开发板上的VCC、SWDIO、SWCLK、GND相连。 对于驱动问题,若是使用Win10、Win11系统,则无需安装驱动,即插即用。在数据线插上电脑USB接口后,可从电脑设备管理器找到。 1.2 软件准备 打开STM32CubeMX,打开MCU SELECTOR,搜索并选中芯片STM32F407VET6: 时钟源配置(时钟树配置参看文章开头的两篇博客): GPIO引脚配置: ① 初始化输出电
[单片机]
【<font color='red'>STM32</font>】1—零基础硬件软件配置 & 完成LED的闪烁
51单片机——数码管动态显示
1、静态与动态不同的显示 在静态显示时所有数码管显示的是一样的,动态显示时可以单独决定每个数码管显示什么; 静态显示时,数码管是常亮的。动态显示时每时刻只有一个数码管在亮; 2、按原理图找对应控制端口 数码管的显示由P0口控制 可以用P1口选择哪个数码管亮 3、程序部分 程序如下: #include reg52.h sbit ADDR0=P1^0; sbit ADDR1=P1^1; sbit ADDR2=P1^2; sbit ADDR3=P1^3; sbit ENLED=P1^4; void delay(unsigned char x); unsigned char code
[单片机]
51<font color='red'>单片机</font>——数码管动态显示
基于ARM微控制器LPC2134的多道脉冲幅度分析器设计
  多道脉冲幅度分析器不仅能自动获取能谱数据,而且一次测量就能得到整个能谱,因此可大大减少数据采集时间,与此同时,其测量精度也显着提高。自从20世纪50年代以来,多道脉冲幅度分析器发展迅速,现在已成为获取核能谱数据的通用仪器。   多道分析任务是将被测量的脉冲幅度范围平均分成2n个幅度间隔,然后测量幅度在每一个幅度间隔内的输入脉冲个数,最后得到输入信号的脉冲幅度分布曲线。其测量采用的是计算机技术中的A/D模数变换及数据存储技术。   在计算机的存储器中开辟一个数据缓冲区,数据缓冲区内有2n个计数器,每一个脉冲幅度间隔在数据缓冲区内部有一个对应的计数器。多道脉冲幅度分析时,可在微处理器的控制下,将被分析的脉冲信号首先送往模数
[单片机]
基于ARM<font color='red'>微控制器</font>LPC2134的多道脉冲幅度分析器设计

推荐帖子

Platform Builder4.2新建平台的问题??
大家好,挽歌为大侠一个为问题!我装了platformBulider4.2是默认安装的,里面只有X86一个型号,新建一个平台的时候,编译平台不能通过,提示下面的错误,大家帮帮忙,告诉我是什么原因???先谢谢了!!eneratingplatformheaderfiles...CEBUILD:DeletingoldbuildlogsCEBUILD:SkippingdirectlytoSYSGENphaseCEBUILD:Runningsysgenprepro
jasonb 嵌入式系统
FPGA串口通信
我用FPGA实现串口通信,使用串口调试助手调试时,只能发送和接收单个字符,我想发送和接收字符串,用verilog怎么实现啊?FPGA串口通信关注能收发单个为什么不能多个呢?有VHDL的好早了呵呵关注,应该可以吧,在调试助手里输入几个字符串,然后发送就可以。串口的ip到处都找得到的。应该可以吧!需要编写一个发送\\接受字符串的协议先写好时序,弄明白串口通信协议!可以一直发送的啊,你只要把数据一直存到发送缓冲区里。。。。发送完毕加一个发送完毕的信号,接收完毕加一个接收完毕的
lin62485145 嵌入式系统
请教下关于示波器探头的使用问题
对示波器不是很了解,想问下,电路中负载的一端接在变压器次级的一脚,另一脚却接在直流电正极,这种情况下,探头应该调直流档还是调交流档去测量?调错会烧吗?请教下关于示波器探头的使用问题【电路中负载的一端接在变压器次级的一脚,另一脚却接在直流电正极】把图贴出来吧。一般来说,是应该调整到直流档位,以便正确测量直流信号的幅值和波形。如果示波器设置错误,比如选择交流档位,可能会导致示波器无法准确显示波形,并且在测量过程中可能会发生损坏。 楼主叙述联接是【电路中负载的一端接在变压器
sky999 测试/测量
请问有没有连接显示屏的压力传感器
搜某宝搜到的直接连接显示屏都是压力变送器,不知道是否有用来测试压力的传感器,测试拉力为几十千牛。急求谢谢!!!请问有没有连接显示屏的压力传感器直接找压力传感器哦,太多了qwqwqw2088发表于2015-4-1415:06直接找压力传感器哦,太多了 您好,我是想找显示屏装在传感器上可以直接读数的那种,这种的压力传感器有吗。newdoll发表于2015-4-1415:27您好,我是想找显示屏装在传感器上可以直接读数的那种,这种的压力传感器有吗。
newdoll 综合技术交流
MSP430147不能复位怎么办?(特急)
我在测试编好的程序时,遇到一个棘手的问题,我是利用MSP430内部的看门狗进行复位的,但是当串口收到高速的数据流(乱码)时,程序就停止运行了,并且C-SPY提示说CPUisoff(lowpowermode),andinterruptsaredisa××ed!我该怎么解决这个问题呢,才能使程序正常运行,并且在收到正确的数据时,能够应答。为什么看门狗没有起作用呢,是不是CPU关闭了,看门狗也就不起作用了呢?MSP430147不能复位怎么办?(特急)低功耗模式用中断唤醒,用穿口的中
myseaweed 微控制器 MCU
高频变压器、低频变压器、脉冲变压器
高频变压器、低频变压器、脉冲变压器1.两个绕组的脉冲变压器和带反馈绕组、有三个绕组的高频变压器的区别?2.为什么高频震荡,电感或者变压器可以做的体积更小?3.在变压器的初级用MOS控制,给予高频的震荡,那么在次级线圈就会有同相位同频率不同幅度的输出?(假设不是1:1的变压器)4.二极管、电容倍压电路的带载能力如何?一般升压的方式有高频变压器直接升压、但是这种方式体积较大;还有用基本的BOOST升压拓扑,采用电感升压的模式;在便携式电子产品中,电池3.7V1000mAh,负载
QWE4562009 分立器件
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved