【STM32】DMA搬运+串口空闲中断读取不定长数据

实现思路

从机通过USART2经过MAX485模块一个字节一个字节发送到主机--->

主机检测到USART2有数据输入然后DMA把数据转运到接收数据缓冲区--->

从机中一帧的最后一个字节发送完 主机触发USART2的空闲中断--->

最后在USART2的中断服务函数中获取接收数据的字节数

实现思路不是特别复杂,下面我们就在代码的海洋里快乐地玩耍吧

硬件要求

两块STM32F103C8T6核心板+两块MAX485模块

实现的功能是两块STM32F103C8T6核心板分别作为主机和从机,通过MAX485模块实现不定长数据的接收和发送

代码解析

变量以及缓冲区定义

/*接收数据缓冲区,最多接受64个字节*/
u8 ReceiveBuffer[64];  
/*每一次不定长帧的长度*/
u8 FrameSize = 0;
/*ReceiveStatus指示数据读取的状态,1表明读取过一次数据,0表明没有读取过数据*/
u8 ReceiveStatus = 0;   
/*一次DMA传输转运的最大字节数*/
u16 DMA1_BufferSize=64;	 

DMA1_BufferSize要大于一帧传输的数据量,不然会造成后续的数据丢失

USART2_IRQHandler(void);

串口中断处理函数

void USART2_IRQHandler(void)
{  
 /*空闲中断标志位判断*/
 	if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET) 
	{	 
		/*如果没有接收过数据,那么就开始执行if语句*/
		  if(ReceiveStatus == 0)
	    {
				/*暂时关闭DMA通道,防止执行下面函数的时候受到干扰*/
		        DMA_Cmd(DMA1_Channel6, DISABLE); 
                /*获取这一帧的数据长度*/				
		        FrameSize = DMA1_BufferSize - DMA_GetCurrDataCounter(DMA1_Channel6);
				/*可以在这里重新设置DMA1_BufferSize,我是没有修改仍然保持64位的*/
		        DMA_SetCurrDataCounter(DMA1_Channel6,DMA1_BufferSize);
				/*重新打开DMA通道*/
				DMA_Cmd(DMA1_Channel6, ENABLE);
				
				/*空闲中断的标志位的清除,需要先读SR寄存器,再读DR寄存器*/
				USART_ReceiveData(USART2);
				
				
				/*已经获取到帧的长度,帧的数据也被存在ReceiveBuffer接收数据缓冲区,读取状态置1*/
				ReceiveStatus = 1;
 	    }  
  }	
} 

1,DMA_GetCurrDataCounter(DMA1_Channel6)函数的功能是计算我们设定的DMA1_BufferSize还剩多少字节没有传输,总的字节数DMA1_BufferSize(64个)减去还没有传输的字节数就等于已经传输的字节数,也就是这一帧的字节数。

2,空闲中断的标志位清除,直接调用USART_ClearITPendingBit是无法清除的

手册中说需要先读USART_SR(对应的函数是USART_GetITStatus(USART2, USART_IT_IDLE)),然后读USART_DR(对应的函数就是USART_ReceiveData(USART2))

RS485_Init(u32 bound);

RS485总线初始化函数

	/**
  * 函    数:RS485初始化
  * 参    数:bound:串口波特率
  * 返 回 值:
  * 注意事项:
  */					  
void RS485_Init(u32 bound)
{  
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
 	NVIC_InitTypeDef NVIC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA传输
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				     //PA0端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;				     //PA1端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;	           //PA2端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	     //复用推挽
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;             //PA3端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  

	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,ENABLE);//复位串口2
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,DISABLE);//停止复位
 
	
 
	USART_InitStructure.USART_BaudRate = bound;          //波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度
	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);                   //初始化串口
  
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
	NVIC_Init(&NVIC_InitStructure);                   //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
 
    USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);   //开启中断
    USART_Cmd(USART2, ENABLE);                       //使能串口 

 
	
    DMA_DeInit(DMA1_Channel6);   //将DMA的通道1寄存器重设为缺省值
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART2->DR;  //DMA外设ADC基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ReceiveBuffer;  //DMA内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //数据传输方向,从外设读取发送到内存
	DMA_InitStructure.DMA_BufferSize = DMA1_BufferSize;  //DMA通道的DMA缓存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA1_Channel6, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
	
	DMA_ClearFlag(DMA1_FLAG_TC6);              //清除DMA标志位
	DMA_ITConfig(DMA1_Channel6, DMA_IT_TE, ENABLE);
    DMA_Cmd(DMA1_Channel6, ENABLE);  //使能USART2 TX DMA1 所指示的通道 
	USART_DMACmd(USART2,USART_DMAReq_Rx, ENABLE);
	RS485_ReceiveEnable();                           //默认RS485总线是工作在接收状态的
}

 1,USART_DMACmd(USART2,USART_DMAReq_Rx, ENABLE);这个函数相当于USART外设到存储器的使能函数,这个是必须要开启的。如果是存储器到存储区的DMA传输,就不需要开启。

2,RS485_ReceiveEnable();默认RS485总线是工作在接收状态的哈

RS485_ReceiveEnable(void)和RS485_SendEnable(void);

接收和发送使能函数

/**
  * 函    数:接收使能
  * 返 回 值:无
  * 注意事项:
  */
void RS485_ReceiveEnable(void)
{
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);
	GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
/**
  * 函    数:发送使能
  * 返 回 值:无
  * 注意事项:
  */
void RS485_SendEnable(void)
{
	GPIO_SetBits(GPIOA,GPIO_Pin_0);
	GPIO_SetBits(GPIOA,GPIO_Pin_1);
}

 1,接收使能和发送使能本质上是对MAX485模块上的DE和RE引脚高低电平的控制

DE和RE引脚均为高电平时,模块工作在发送状态。

DE和RE引脚均为低电平时,模块工作在接收状态。

RS485_SendData(u8 *buf,u8 len)和RS485_ReceiveData(u8 *buf);

RS485发送和接收多个字节

/**
* 函    数:RS485发送len个字节.
* 返 回 值:buf:发送区首地址
* 返 回 值:len:发送的字节数
* 注意事项:
*/

void RS485_SendData(u8 *buf,u8 len)
{
	int t;
	/*开启发送状态*/
	RS485_SendEnable();
	/*循环发送数据*/
  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);		
	/*因为默认是工作在接收状态的,所以重新开启接收使能*/
	RS485_ReceiveEnable();                        
}
/**
  * 函    数:RS485从数据缓冲区读取len个字节.
  * 返 回 值:buf:接收区首地址
  * 返 回 值:RS485_RX_CNTn:数据缓冲区接收的数据个数
  * 注意事项:
  */
void RS485_ReceiveData(u8 *buf)
{
	/*判断接受状态,如果接收过,那么进入if语句*/
	if(ReceiveStatus == 1)
	{
	   int i;
	   for(i=0;i<FrameSize;i++)
	   {
	    	buf[i]=ReceiveBuffer[i];	
	   }		
		 /*接收完毕,重新把ReceiveStatus置为0*/
	   ReceiveStatus = 0;
  }

1,宇宙无敌天才的你肯定一下就看明白了,连续发送/接收多个字节就是SendData/ReceiveDataT套个循环结构,要注意的是ReceiveData循环的次数是我们中断服务函数获得的帧数据长度FrameSize

RS485.h

#ifndef __RS485_H
#define __RS485_H			 
#include "sys.h"	 								  
	  		  	
extern u8 RS485_RX_BUF[64]; 		//接收缓冲,最大64个字节
extern u8 RS485_RX_CNT;   			//接收到的数据长度
extern u8 FrameSize;            //接收到的帧的数据长度
void RS485_Init(u32 bound);
void RS485_ReceiveEnable(void);
void RS485_SendEnable(void);
void RS485_SendData(u8 *buf,u8 len);
void RS485_ReceiveData(u8 *buf);

#endif	   

百度网盘代码链接

 发送端和接收端工程代码

有什么问题可以评论区留言,看到会给你回复的

  • 28
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值