核心板:STM32F407
实验目的:通过DMA接收串口发来的数据,并且利用串口空闲中断在将这些数据发送至串口助手。
在通常我们使用串口接收数据时,会使能串口接收中断,并在每次接收到数据后会进入中断将其进行采集。
USART_ITConfig(USART,USART_IT_RXNE,ENABLE);//开启接收中断
/* 相关变量没有展示 */
void USART_IRQHandler(void)
{
if(USART_GetFlagStatus(USART, USART_FLAG_RXNE) != RESET)
{
Rec[Cnt++] = USART_ReceiveData(USART);
if(Rec[Cnt-1] == 0x0A && Rec[Cnt-2] == 0x0D)
{
Rec[Cnt] = '\0';
printf("接收到的数据为:%s",Rec);
Cnt = 0;
}
}
}
以上方法在每次接收到数据进入中断服务函数时都会判断是否为接收完成,效率非常低,因此我们使用DMA+串口空闲中断的方法来进行接收与发送数据。
DMA:非常nb的搬运工,可以极大的减少CPU的负担。
串口的空闲中断:检测到空闲线路的时候会产生中断,那么何为空闲线路?我们发一堆数据过去,连续的两个数据中肯定有时间间隔,很明显在这段时间内并不叫空闲;空闲中断其实也叫帧中断,即在总线上在一个字节的时间内没有再次接收到数据,那么此时就会产生中断,即空闲中断。
以下图片是F407中文手册中对于串口DMA的使用方法。
编程思路:主要配置DMA与编写串口空闲中断的服务函数。配置DMA发送时,将Rec作为源地址,USART_DR(数据寄存器 )作为目标地址,并将DMA的传输模式配置为一次传输,配置好其他的数据后将其失能,因为我们在中断服务函数中将其使能。配置DMA收送时,将Rec作为目标地址,USART_DR(数据寄存器 )作为源地址,并将DMA的传输模式配置为循环模式,配置好其他的数据后将其使能。下面上代码。
USART接收与发送引脚对应的DMA
bsp_usart.h
#ifndef _BSP__USART_H
#define _BSP__USART_H
#include "stm32f4xx.h"
#include <stdio.h>
#define USART USART1
#define USART_CLK RCC_APB2Periph_USART1
#define USART_BAUDRATE 115200
/* 串口Tx——PA9 */
#define USART_TX_PIN GPIO_Pin_9
#define USART_TX_GPIO_PORT GPIOA
#define USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define USART_TX_SOURCE GPIO_PinSource9
#define USART_TX_AF GPIO_AF_USART1
/* 串口Rx——PA10 */
#define USART_RX_PIN GPIO_Pin_10
#define USART_RX_GPIO_PORT GPIOA
#define USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define USART_RX_SOURCE GPIO_PinSource10
#define USART_RX_AF GPIO_AF_USART1
#define USART_IRQ USART1_IRQn
#define USART_IRQHandler USART1_IRQHandler
/* DMA */
#define USART_DR_BASE (USART1_BASE+0x04)
#define USART_TX_DMA_CLK RCC_AHB1Periph_DMA2
#define USART_TX_DMA_CHANNEL DMA_Channel_4
#define USART_TX_DMA_STREAM DMA2_Stream7
#define USART_RX_DMA_CLK RCC_AHB1Periph_DMA2
#define USART_RX_DMA_CHANNEL DMA_Channel_4
#define USART_RX_DMA_STREAM DMA2_Stream5
#define RX_MAX_LEN 255
extern char Rec[RX_MAX_LEN];
void USART_Config(void);
#endif
bsp_usart.c
#include "bsp_usart.h"
char Rec[RX_MAX_LEN];
static void USART_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = USART_IRQ;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStructure);
}
static void USART_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(USART_TX_DMA_CLK | USART_RX_DMA_CLK, ENABLE);//使能DMA2时钟
/* 配置发送 */
DMA_InitStructure.DMA_BufferSize = 0;//随便配置,因为在接收到数据后我们会重新给这值赋值
DMA_InitStructure.DMA_Channel = USART_TX_DMA_CHANNEL;//串口发送通道
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//DMA搬运方向:存储器到外设
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Rec;//存储器地址
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择:单次模式
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据宽度:字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable ;//使能存储器地址自动递增功能
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA 传输模式选择:一次传输
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_BASE;//外设地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择:单次模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度:字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//禁止外设地址自动递增功能
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//软件设置数据流的优先级:中等
/* FIFO不用随便配置 */
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_Init(USART_TX_DMA_STREAM, &DMA_InitStructure);
DMA_Cmd(USART_TX_DMA_STREAM, DISABLE);//发送先失能
/* 配置接收 */
DMA_InitStructure.DMA_BufferSize = RX_MAX_LEN;//接收数据的长度
DMA_InitStructure.DMA_Channel = USART_RX_DMA_CHANNEL;//串口发送通道
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//DMA搬运方向:外设到存储器
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA 传输模式选择:循环接收
/* 其余配置与上面一样 */
DMA_Init(USART_RX_DMA_STREAM, &DMA_InitStructure);
DMA_Cmd(USART_RX_DMA_STREAM, ENABLE);//接收使能
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 使能时钟 */
RCC_AHB1PeriphClockCmd(USART_TX_GPIO_CLK | USART_RX_GPIO_CLK,ENABLE);
RCC_APB2PeriphClockCmd(USART_CLK, ENABLE);
/* GPIO相关配置 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF ;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//无上拉 无下拉
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed ;//高速
GPIO_InitStructure.GPIO_Pin = USART_TX_PIN;//配置发送引脚
GPIO_Init (USART_TX_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = USART_RX_PIN;//配置接收引脚
GPIO_Init (USART_RX_GPIO_PORT,&GPIO_InitStructure);
/* 将对应的IO口连接到外设,开始启动复用功能 */
GPIO_PinAFConfig(USART_TX_GPIO_PORT,USART_TX_SOURCE,USART_TX_AF);
GPIO_PinAFConfig(USART_RX_GPIO_PORT,USART_RX_SOURCE,USART_RX_AF);
/* USART1的相关配置 */
USART_InitStructure.USART_BaudRate = USART_BAUDRATE;//波特率115200
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用硬件流
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx ;//USART模式控制:同时使能接收和发送
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位选择:不使用校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位:1个停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b ;//字长(数据位+校验位):8
USART_Init(USART,&USART_InitStructure);
USART_NVIC_Config();//中断配置
USART_DMA_Config();//DMA配置
USART_ITConfig(USART,USART_IT_IDLE,ENABLE);//开启空闲中断
USART_DMACmd(USART, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);//使能DMA接收与发送
USART_Cmd(USART,ENABLE);//使能串口
}
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USART, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(USART, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(USART, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART);
}
中断服务函数
#include "stm32f4xx_it.h"
#include "bsp_usart.h"
void USART_IRQHandler(void)
{
uint16_t temp;
if(USART_GetFlagStatus(USART, USART_FLAG_IDLE) != RESET)
{
temp = USART1->SR;
temp = USART1->DR;//清除IDLE中断
temp = DMA_GetCurrDataCounter(USART_RX_DMA_STREAM);//获取剩余的接收数据的量
DMA_Cmd(USART_RX_DMA_STREAM, DISABLE);
DMA_SetCurrDataCounter(USART_RX_DMA_STREAM, RX_MAX_LEN);//设置需要接收的量
DMA_Cmd(USART_RX_DMA_STREAM, ENABLE);
DMA_Cmd(USART_TX_DMA_STREAM, DISABLE);
DMA_SetCurrDataCounter(USART_TX_DMA_STREAM, RX_MAX_LEN - temp);//设置需要发送的量
DMA_Cmd(USART_TX_DMA_STREAM, ENABLE);
while( USART_GetFlagStatus(USART, USART_FLAG_TC) == RESET );//必须等待,否则最后一位数据错误
DMA_ClearFlag(USART_TX_DMA_STREAM, DMA_FLAG_TCIF7); //清除DMA传输完成标志
USART_ClearFlag(USART,USART_FLAG_TC); //清除传输完成标志
}
}
这里等待数据传输完成时一定要等到TC位置1的(TC位是最后置1的),因此DMA的传输标志在其置1后清除。当然也可以等待DMA传输完成在将DMA的传输标志清除,然后在等待TC位置1,然后清除TC位。第二种更符合逻辑,看不懂看上面使用DMA发送的官方图片。
还有就是对于DMA在中断服务函数中的各种使能使能是来源于与手册的要求。DMA_SetCurrDataCounter()函数操作的是以下寄存器。
主函数
int main(void)
{
USART_Config();
printf("一切OK\n");
while (1)
{
}
}
实验结果:
有一个自己当时理解不了的,就是我们设置DMA接收数据大小为255,但是我们不可能每次都正好的给它发送255个数据,所以每次DMA都会一直等,但是我们使用了空闲中断,可以中断“DMA的等待”。又因为我们接收是循环模式,进入中断后会自动重新设置DMA接收数据的大小为255 。(个人理解,有误请大佬指正)
对于不太清除为什么DMA要不断使能失能可以看一下下面这个文章
以上仅供自己与大家学习积累,欢迎各位大佬批评与指正!