stm32串口Usart接收不定长数据(RXNE和DMA两种方法)

目录

前言

一、RXNE+IDLE方法

1.寄存器简介 

2.代码

二、DMA+IDLE方法

1.寄存器简介

2.代码


前言

在学习了串口数据包添加包头包尾的方法来接收文本数据包后,拜读了一些大佬的文章,发现可以利用USART_SR寄存器的IDLE来检测总线在何时处于空闲状态,这样就可以在利用RXNE中断标志或DMA读取一串数据后知道何时结束这一串数据。

一、RXNE+IDLE方法

1.寄存器简介 

(来源于f10xxx参考手册)

RXNE:读数据寄存器非空 (Read data register not empty) 
当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果
USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清
零。RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。
0:数据没有收到;
1:收到数据,可以读出。

IDLE:监测到总线空闲 (IDLE line detected) 
当检测到总线空闲时,该位被硬件置位。如果USART_CR1中的IDLEIE为’1’,则产生中断。由
软件序列清除该位(先读USART_SR,然后读USART_DR)。
0:没有检测到空闲总线;
1:检测到空闲总线。
注意:IDLE位不会再次被置高直到RXNE位被置起(即又检测到一次空闲总线) 。
(这里可以看出退出IDLE的方法是先读USART_SR,然后读USART_DR)。

USART收发数据都会有一个起始bit和终止bit,起始bit变为低电平,终止bit升为高电平。空闲时一般为高电平。

fa374463aa384072ad458336190b8f60.png

2.代码

代码如下

main.c

#include "stm32f10x.h"
#include "usart1.h"

uint8_t Rx_buff[100];    //这里假设存储上限为100
uint16_t Rx_num = 0 ;    //存储计数标志位
uint8_t Rx_done;         //总线空闲中断标志位
int main(void)
{
   Usart1_Init();      
   while (1)
	{
   if (Rx_done==1)                //判断是否出现空闲
	 {
		 Rx_done=0;               //标志位清零
		 if (Rx_num >100)         //输入超过存储上限会报错
		 {
			 Serial_Printf("error");       
			 Rx_num =0;            //计数位置零
		 }
		 else 
		 {
			 Serial_sendarray(Rx_buff,Rx_num);    //串口输出输入的数据,位长由Rx_num确定
		   Rx_num =0;                             //计数位置零
		 }
	 }
  }
}

usart1.c

#include "stm32f10x.h" 
#include "stdarg.h"
#include "stdio.h"                 
uint16_t RXFlag ;
extern uint8_t Rx_buff[100];
extern uint16_t Rx_num  ;
extern uint8_t Rx_done;
void Usart1_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  //开启时钟
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;           //复用推挽输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;            //上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOA, &GPIO_InitStruct);
	
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;            //串口波特率9600;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
	USART_InitStruct.USART_Mode = USART_Mode_Tx| USART_Mode_Rx;       //收发均有
	USART_InitStruct.USART_Parity = USART_Parity_No; 
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStruct);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//RXNE中断使能
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//IDLE中断使能
	
	USART_Cmd(USART1, ENABLE);                 //串口使能
	
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(& NVIC_InitStruct);            //NVIC的一系列初始化配置
}
void Serial_senddata(uint16_t byte)
{
	USART_SendData(USART1, byte);
	while(USART_GetFlagStatus( USART1, USART_FLAG_TXE)== RESET);  
		
}                    //调用标准库的USART_SendDataTX,发一字节函数

void Serial_sendarray(uint8_t array[],uint16_t length)
{
	uint16_t i;
	for(i=0 ;i<length ;i++)
	Serial_senddata(array[i] );
}                                              //发送一串已知长度的数组的数据函数

void Serial_sendstring(char *string)
{
	uint16_t i;
	for(i=0 ;string[i]!='\0';i++)
	Serial_senddata(string[i] );
}                                             //发送字符串函数

void Serial_Printf(char *format,...)
{
	char string[100];
	va_list arg;
	va_start (arg,format);
	vsprintf (string ,format,arg);
	va_end(arg);
	Serial_sendstring (string);
}                                            //将格式化的字符串输出到串口函数
  
void USART1_IRQHandler()
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE)== SET)
	{
		Rx_buff[Rx_num++] = USART_ReceiveData(USART1);       //RXNE读数据寄存器非空,开始读取一串连续的数据,直到空闲出现

	}
	if (USART_GetITStatus(USART1, USART_IT_IDLE)==SET)
	{
		USART1->SR;
		USART1->DR;        //IDLE位清零
		Rx_done=1;         //标志位置1,代表空闲出现,可以读取数据
	}
}

usart1.h

#ifndef __Usart1_H
#define __Usart1_H
void Usart1_Init(void);
void Serial_senddata(uint16_t byte);
void Serial_sendarray(uint8_t *array,uint16_t length);
void Serial_sendstring(char *string);
void Serial_Printf(char *format,...);
#endif

借助串口调试助手,可以看到发送区写一串数据点击发送后,在接收区收到发送的数据,说明每一次发送的数据都存到了Rx_buff[ ]里。d03209f2ceaa46c6b0451725610396a7.png

二、DMA+IDLE方法

1.寄存器简介

上面那种接受一位数据触发一次中断的方法虽然可以实现对于不定长数据的接收,而且在CPU处理任务量不大的时候速度和准确率都还可以(起码只开启USART1的RX和TX在串口调试助手上做实验是这样的)。但是感觉用CPU来处理每一次的中断触发任务来实现数据的搬移有些浪费资源。所以这里再给出一种DMA配合串口接收的方法。

(来源于f10xxx参考手册) 

DMA简介
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。(DMA2仅存在于大容量产品和互联型产品)。

DMA总线
此总线将DMA的AHB主控接口与总线矩阵相联,总线矩阵协调着CPU的DCode和DMA到SRAM、闪存和外设的访问。
 

整个输入存储过程可以概括为3步:1.外设向DMA发出请求。2.DMA接受请求开始工作。3.在仲裁器,DMA总线和总线矩阵的协调下,将外设的数据经由AHB总线道通传到专用的7个通道之一的某个通道,最后直接存到SRAM里,不需CPU(系统总线)参与。

2.代码

main.c

#include "stm32f10x.h"                  // Device header
#include "usart1.h"
#include "myDMA.h"

uint16_t DMA_Count;                   //DMA某一次搬运数据大小
uint8_t Serial_Rxflag;                //IDLE中断判断标志
uint8_t Rxdata[200];                  //在SRAM里开辟的存储区,用来存从DMA运来的数据
    
int main(void)
{
  Usart1_Init();                       //串口初始化
  myDMA_Init((uint32_t)&USART1->DR,(uint32_t)Rxdata);       //DMA初始化+第一次启动
  while(1)
	{
		if (Serial_Rxflag == 1)
		{
			Serial_Rxflag = 0;                        
			Serial_sendarray(Rxdata,DMA_Count);            //发送数据到串口
		} 
	}
   
}

myDMA.c

#include "stm32f10x.h"                  // Device header

uint16_t myDMA_Size = 200;              //DMA数据最大传输数量为200,即从200开始递减。该值范围0~65535
extern uint16_t DMA_Count ;

void myDMA_Init(uint32_t AddrA,uint32_t AddrB)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_BufferSize = myDMA_Size;
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;       //指定外设为源端
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;            //非存储器到存储器模式
	DMA_InitStruct.DMA_MemoryBaseAddr = AddrB;           //存储器地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;     //存储器数据宽度
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;      //存储器地址递增
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                //不选择自动重装后循环传输
	DMA_InitStruct.DMA_PeripheralBaseAddr = AddrA;             //外设地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    //外设数据宽度
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;         //外设地址不递增
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;        //设通道优先级

	DMA_Init(DMA1_Channel5, &DMA_InitStruct);                //DMA初始化
	
	DMA_Cmd(DMA1_Channel5, ENABLE);                          //启动DMA
}

void myDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel5, DISABLE);
  while (DMA_GetFlagStatus(DMA1_FLAG_TC5==RESET));
  DMA_ClearFlag(DMA1_FLAG_TC5);                            //确保DMA的传输完成
	DMA_Count = myDMA_Size - DMA_GetCurrDataCounter(DMA1_Channel5);     //查看一次转运的数据个数
	DMA_SetCurrDataCounter(DMA1_Channel5, myDMA_Size);         //再次装上BufferSize,必须禁用DMA时才能使用
	DMA_Cmd(DMA1_Channel5, ENABLE);
}

 myDMA.h

#ifndef __MYDMA_H
#define __MYDMA_H
void myDMA_Init(uint32_t AddrA,uint32_t AddrB);
void myDMA_Transfer(void);
#endif

usart1.c

#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdarg.h"
#include "myDMA.h"

extern uint8_t Serial_Rxflag;

void Usart1_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;           
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;            
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOA, &GPIO_InitStruct);
	
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;            
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
	USART_InitStruct.USART_Mode = USART_Mode_Tx| USART_Mode_Rx;       
	USART_InitStruct.USART_Parity = USART_Parity_No; 
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStruct);
	
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//IDLE中断使能

	USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);//DMA使能接收
	
	USART_Cmd(USART1, ENABLE);                
	
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(& NVIC_InitStruct);            
}
void Serial_senddata(uint16_t byte)
{
	USART_SendData(USART1, byte);
	while(USART_GetFlagStatus( USART1, USART_FLAG_TXE)== RESET);  
		
}                   

void Serial_sendarray(uint8_t array[],uint16_t length)
{
	uint16_t i;
	for(i=0 ;i<length ;i++)
	Serial_senddata(array[i] );
}                                              

void Serial_sendstring(char *string)
{
	uint16_t i;
	for(i=0 ;string[i]!='\0';i++)
	Serial_senddata(string[i] );
}                                             

void Serial_Printf(char *format,...)
{
	char string[100];
	va_list arg;
	va_start (arg,format);
	vsprintf (string ,format,arg);
	va_end(arg);
	Serial_sendstring (string);
}                                           
  
void USART1_IRQHandler()
{
	if (USART_GetITStatus(USART1, USART_IT_IDLE)==SET)        //判断总线空闲
	{
		myDMA_Transfer();
		Serial_Rxflag = 1;                //给出空闲标志
		USART1 ->SR;
		USART1 ->DR;          //置零IDLE标志位
		
	}
}

 usart1.h

#ifndef __Usart1_H
#define __Usart1_H
void Usart1_Init(void);
void Serial_senddata(uint16_t byte);
void Serial_sendarray(uint8_t *array,uint16_t length);
void Serial_sendstring(char *string);
void Serial_Printf(char *format,...);
#endif

这里的实验和上面的实验结果类似,一样是用串口调试助手做的测试,不再累述。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个使用STM32F407标准库,通过USART2和DMA进行串口数据接收的示例代码。 首先,需要初始化USART2和DMA。以下是初始化USART2和DMA的函数: ```c void USART2_DMA_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; DMA_InitTypeDef DMA_InitStructure; // 使能GPIOA时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 使能USART2时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 使能DMA1时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); // 配置PA2为USART2的TX引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA3为USART2的RX引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 将PA2和PA3连接到USART2 GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); // 配置USART2 USART_InitStructure.USART_BaudRate = 115200; 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(USART2, &USART_InitStructure); // 配置DMA1_Stream5 DMA_DeInit(DMA1_Stream5); DMA_InitStructure.DMA_Channel = DMA_Channel_4; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_BufferSize = 100; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR); DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)usart2_rx_buffer; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA1_Stream5, &DMA_InitStructure); // 使能USART2和DMA1_Stream5 USART_Cmd(USART2, ENABLE); DMA_Cmd(DMA1_Stream5, ENABLE); // 使能USART2的DMA接收 USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); } ``` 在上述代码中,通过GPIO_Init()函数配置USART2的TX和RX引脚,然后通过USART_Init()函数配置USART2。使用DMA_Init()函数初始化DMA1_Stream5,并将其连接到USART2的DR寄存器。最后,使用USART_DMACmd()函数启用USART2的DMA接收。 接下来,需要设置一个DMA接收完成中断(Transfer Complete interrupt)。以下是设置DMA接收完成中断的函数: ```c void DMA1_Stream5_IRQHandler(void) { if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5) != RESET) { DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5); DMA_Cmd(DMA1_Stream5, DISABLE); DMA_SetCurrDataCounter(DMA1_Stream5, 100); DMA_Cmd(DMA1_Stream5, ENABLE); } } ``` 在上述代码中,使用DMA_GetITStatus()函数检查DMA接收完成中断是否已被触发。如果中断被触发,使用DMA_ClearITPendingBit()函数清除中断标志,并使用DMA_Cmd()函数禁用DMA1_Stream5。最后,使用DMA_SetCurrDataCounter()函数将DMA1_Stream5的数据计数器重置为100,并使用DMA_Cmd()函数重新启动DMA1_Stream5。 现在,可以使用以下代码在主函数中调用上述函数来初始化USART2和DMA: ```c int main(void) { USART2_DMA_Init(); while (1) { } } ``` 最后,可以使用以下代码从usart2_rx_buffer数组中读取接收到的数据: ```c uint8_t usart2_rx_buffer[100]; uint8_t usart2_rx_buffer_index = 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { usart2_rx_buffer[usart2_rx_buffer_index++] = USART_ReceiveData(USART2); if (usart2_rx_buffer_index >= 100) { usart2_rx_buffer_index = 0; } } } ``` 在上述代码中,使用USART_GetITStatus()函数检查USART2是否已接收数据。如果有数据可用,使用USART_ReceiveData()函数将数据读取到usart2_rx_buffer数组中。最后,检查usart2_rx_buffer_index是否超过了数组的大小,如果是,则将其重置为0。 这是一个简单的使用STM32F407标准库通过USART2和DMA进行串口数据接收的示例。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值