在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成。但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志,对于这样的数据通常的做法是每隔一段时间查看一下接收数据的长度是否发生了变化,如果指定的一段时间内接收数据长度没有发生变化,就认为是一帧数据发送完成。在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;
上一篇:STM32单片机串口空闲中断+DMA接收不定长数据
下一篇:STM32F103单片机串口通信带奇偶校验位
推荐阅读最新更新时间:2024-11-09 13:42
推荐帖子
- 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 分立器件
设计资源 培训 开发板 精华推荐
- LT1086IT-12 1.2V 至 15V 可调稳压器的典型应用
- 使用 IXYS 的 MX879 的参考设计
- 用于无线语音音频的射频收发器
- #第八届立创电赛#电子时钟_2023暑期训练营
- 具有启用和复位功能的 NCV87725D7S50R4G 350mA、5V 输出电压 LDO 线性稳压器的典型应用原理图
- 用于简单时钟振荡器的 NCP301LSN33T1 3.3V 电压检测器的典型应用
- A8584 同步降压稳压器的典型应用 @ VIN = 12V,VOUT = 3.3V,fSW = 425 kHz
- LTC3537 的典型应用 - 2.2 MHz、600mA 同步升压型 DC/DC 转换器和 100mA LDO
- L78L12AC 电流调节器的典型应用
- DC996B-H,演示板,具有 LTC2216 16 位 80 Msps、高速和高动态范围 ADC