STM32F4无人机实现串口+DMA数据收发

更多交流欢迎关注作者抖音号:81849645041

目的

        了解DMA 的工作原理,通过配置 STM32F407 芯片的DMA,来完成通过 DMA 实现串口数据收发的基础实验。

原理

        基于USART的数据通讯中采用中断方式可以在接收到信息或需要发送数据时产生中断,在中断服务程序中完成数据的接收与发送。但是中断方式的CPU使用率要高。在简单的系统中,使用中断方式确实是一种好方法。但是在复杂的系统中,处理器需要处理串行口通信,多个传感器数据的采集以及处理,牵扯到多个中断的优先级分配问题。为了保证数据发送与接收的可靠性,需要把USART的中断优先级设计较高,但是系统可能还有其他的需要更高优先级的中断,必须保证其定时的准确,这样就有可能造成串行通讯的中断不能及时响应,从而造成数据丢失。 为了保证串行通讯的数据及时可靠的接收,同时兼顾其它任务不受影响,采用了基于DMA和中断方式相结合的USART串行通信方式。

        DMA,全称:Direct  Memory  Access,直接存储器访问用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。

        DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结合在一起,优化了系统带宽。

        STM32F4xx 有两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

        DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来启动 AHB 事务。

        它可以执行下列事务:

        ● 外设到存储器的传输

        ● 存储器到外设的传输

        ● 存储器到存储器的传输

        DMA寄存器包括:低中断状态寄存器(DMA_LISR)、高中断状态寄存器(DMA_HISR)、低中断标志清零寄存器(DMA_LIFCR)、高中断标志清零寄存器(DMA_HIFCR)、数据流x配置寄存器(DMA_SxCR)、数据流x数据项数寄存器(DMA_SxNDTR)、数据流x外设地址寄存器(DMA_SxPAR)、数据流x存储器0地址寄存器(DMA_SxM0AR)、数据流x存储器1地址寄存器(DMA_SxM1AR)、数据流xFIFO控制寄存器(DMA_SxFCR)。

        低中断状态寄存器(DMA_LISR):

高中断状态寄存器(DMA_HISR):

数据流x配置寄存器(DMA_SxCR):

 数据流x数据项数寄存器(DMA_SxNDTR):

 数据流x外设地址寄存器(DMA_SxPAR):

 说明:以上介绍的是DMA常用的一部分寄存器,具体详细资料请参照STM32F4xx参考手册。

准备

        MDK5开发环境的成功安装。

        STM32F4xx标准外设库。

        STM32F407飞控板。

        STM32F4xx 参考手册。

        飞控板电路原理图。

        串口调试助手软件。

        USB转TTL模块。

步骤

  • 查看 STM32F407数据表的时钟框图,了解到 DMA 连接在AHB1时钟线上,因此若使用 DMA 必须打开 AHB1时钟。

  •  查看STM32F407 参考手册 DMA 通道列表可知,串口1的RX在DMA2上的数据流2通道4以及数据流5通道4、TX在DMA2的数据流7通道4(在这里串口1RX选用的是数据流2通道4)。

  •  在工程中新建两个文件分别命名为bsp_usart_dma.c和bsp_usart_dma.h,并将文件保存并添加在该工程文件夹中。

  •  在bsp_usart_dma.c文件中添加相应头文件
#include "bsp_usart_dma.h"
  • 在bsp_usart_dma.c文件中定义接收缓冲区结构体变量,定义USART1_DMA2_Init()函数。
// 定义接受缓冲区结构体变量
USART1_DMA_DATA usart1_data;
// DMA初始化配置函数
void USART1_DMA2_Init(void)
{
}
  • 在USART1_DMA2_Init()函数中实现串口的DMA接收数据的配置。

        第一步:使能DMA2的时钟

        第二步:定义DMA_InitStruct_TX结构体变量并初始化DMA2_Stream7

        第三步:配置DMA串口发送完成中断

        第四步:定义DMA_InitStruct_RX结构体变量并初始化DMA2_Stream2,使能DMA2_Stream2

        第五步:使能USART的DMA接口、使能串口1

void USART1_DMA2_Init(void)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 , ENABLE);
	//配置DMA2_Stream7
	DMA_DeInit(DMA2_Stream7); // 复位
	
	DMA_InitTypeDef DMA_InitStruct_TX;
	
	DMA_InitStruct_TX.DMA_BufferSize = sizeof(usart1_data.tx_buf); // 发送缓存区大小
	DMA_InitStruct_TX.DMA_Channel = DMA_Channel_4; // 设置通道
	DMA_InitStruct_TX.DMA_DIR = DMA_DIR_MemoryToPeripheral; // 内存到外设
	DMA_InitStruct_TX.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStruct_TX.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
	DMA_InitStruct_TX.DMA_Memory0BaseAddr = (uint32_t)&usart1_data.tx_buf[0]; // 存储区基地址
	DMA_InitStruct_TX.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 突发模式
	DMA_InitStruct_TX.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStruct_TX.DMA_MemoryInc = DMA_MemoryInc_Enable; // 增加存储器地址寄存器
	DMA_InitStruct_TX.DMA_Mode = DMA_Mode_Normal; // 标准模式
	DMA_InitStruct_TX.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // 外设地址
	DMA_InitStruct_TX.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_InitStruct_TX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStruct_TX.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 关闭增加外设地址寄存器
	DMA_InitStruct_TX.DMA_Priority =DMA_Priority_High;
	DMA_Init(DMA2_Stream7 , &DMA_InitStruct_TX);
	
	//使能DMA串口中断
	NVIC_InitTypeDef NVIC_InitStruct;
	
	NVIC_InitStruct.NVIC_IRQChannel = DMA2_Stream7_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 6;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStruct);
	
	DMA_ITConfig(DMA2_Stream7 , DMA_IT_TC , ENABLE); // 传输中断使能
	USART_DMACmd(USART1 , USART_DMAReq_Tx ,ENABLE); // USART_DAM使能
	
	// 配置DMA2_Stream2
	DMA_DeInit(DMA2_Stream2);
	
	DMA_InitTypeDef DMA_InitStruct_RX;
	
	DMA_InitStruct_RX.DMA_BufferSize = sizeof(usart1_data.rx_buf); // 传输缓冲区大小
	DMA_InitStruct_RX.DMA_Channel = DMA_Channel_4; // 设置通道
	DMA_InitStruct_RX.DMA_DIR = DMA_DIR_PeripheralToMemory; // 外设到内存
	DMA_InitStruct_RX.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStruct_RX.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
	DMA_InitStruct_RX.DMA_Memory0BaseAddr = (uint32_t)&usart1_data.rx_buf[0]; // 存储区基地址
	DMA_InitStruct_RX.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 突发模式
	DMA_InitStruct_RX.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStruct_RX.DMA_MemoryInc = DMA_MemoryInc_Enable; // 增加存储器地址寄存器
	DMA_InitStruct_RX.DMA_Mode = DMA_Mode_Circular;  // 循环模式
	DMA_InitStruct_RX.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // 外设地址
	DMA_InitStruct_RX.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_InitStruct_RX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStruct_RX.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 关闭增加外设地址寄存器
	DMA_InitStruct_RX.DMA_Priority = DMA_Priority_High;
	DMA_Init(DMA2_Stream2 , &DMA_InitStruct_RX); // 初始化
	
	USART_DMACmd(USART1 , USART_DMAReq_Rx , ENABLE); // 启用USART的DMA接口
	
	DMA_Cmd(DMA2_Stream2 , ENABLE);
	
	USART_Cmd(USART1, ENABLE); // 使能串口1
}
  • 在bsp_usart_dma.c文件中定义DMA2_Stream7传输完成中断服务函数
//USART1_TX DMA2_Stream7传输完成中断服务函数
void DMA2_Stream7_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA2_Stream7 , DMA_IT_TCIF7) != RESET)
	{
	    DMA_ClearFlag(DMA2_Stream7, DMA_IT_TCIF7);
	    DMA_Cmd(DMA2_Stream7, DISABLE);
	}
}
  • 在bsp_usart_dma.c文件中定义数据解析函数,将rx_buf缓冲区数据拷贝到tx_buf中,再通过DMA2_Stream7将输出传输到串口发送寄存器中,最后在串口助手中显示出来。
//数据解析
void DMA2_Parse_Message(void)
{
	static uint16_t last_pos = 0; // 上一次接收数据位置
	uint16_t pos = sizeof(usart1_data.rx_buf) - DMA2_Stream2->NDTR; // 当前接收数据位置
	if(pos == last_pos)
	{
	    return;
	}
	if(pos > last_pos)
	{
	    memcpy(usart1_data.tx_buf, usart1_data.rx_buf+last_pos, pos-last_pos);
	    DMA_SetCurrDataCounter(DMA2_Stream7 , pos-last_pos);
	}
	else
	{
        memcpy(usart1_data.tx_buf, usart1_data.rx_buf+last_pos, sizeof(usart1_data.rx_buf)-last_pos);
	    memcpy(usart1_data.tx_buf+(pos-last_pos), usart1_data.rx_buf , pos);
	    DMA_SetCurrDataCounter(DMA2_Stream7 , sizeof(usart1_data.rx_buf)-last_pos+pos);
 	}
	last_pos = pos;
	DMA_Cmd(DMA2_Stream7 , ENABLE);
}
  • 在bsp_dma.h文件中定义结构体进行函数声明。
#ifndef __BSP_USART_DMA_H__
#define __BSP_USART_DMA_H__

#include "stm32f4xx.h"
#include <string.h>

typedef struct
{
	uint8_t tx_buf[20];
	uint8_t rx_buf[20];
} USART1_DMA_DATA;

void USART1_DMA2_Init(void);
void DMA2_Parse_Message(void);
#endif
  • 在main.c文件中调用相关函数。

        第一步:引用相关的头文件

        第二步:初始化滴答定时器、串口以及串口的DMA

        第三步:记录开始时间,第四步:调用数据解析函数。

#include "bsp_systick.h"
#include "bsp_usart.h"
#include "bsp_usart_dma.h"

int main(void)
{·
	SysTick_Init(1);
	USART1_Init(115200); // 串口1初始化函数调用,设置波特率
	USART1_DMA2_Init(); // DMA2初始化
	while(1)
	{
	    DMA2_Parse_Message(); // 数据解析
	    SysTick_DelayMS(50); 
	}
}

现象

        将程序下载到开发板后,打开串口调试助手,多字符串发送数据,自动循环发送,虽然发送间隔为30ms,解析间隔为50ms,但由于DMA缓冲区的存在,可以保证数据全部被正确接收,接收缓冲区中显示了DMA接收到并发送出来的数据。

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值