网站 中国最早做网站的,企业样本设计公司,中国建设行业网官网,安阳做网站哪家好DMA实现数据发送 文章目录 DMA实现数据发送前言一、DMA二、代码编写1.DMA2.USART3.main 前言
当你遇到通信数据量大的时候#xff0c;可以使用 空闲中断 DMA 的方案来减轻 CPU 的压力。 或者 在进行stm32开发时#xff0c;有时会遇到这种情况#xff1a;需要在设备间进行数…DMA实现数据发送 文章目录 DMA实现数据发送前言一、DMA二、代码编写1.DMA2.USART3.main 前言
当你遇到通信数据量大的时候可以使用 空闲中断 DMA 的方案来减轻 CPU 的压力。 或者 在进行stm32开发时有时会遇到这种情况需要在设备间进行数据传输由于stm32串口RDR和TDR寄存器都是8位有效的我们往往需要定义传输协议(如一帧数据中包含包含帧头、帧ID、数据帧、校验帧等若干8位数据)。我们希望可以一次收到一帧数据并进行解码操作。利DMA串口空闲中断可以有效完成上述任务。
一、DMA
1、简介 DMA(直接存储器访问)是一种数据传输方法利用DMA控制器将数据直接从一个地址空间复制到另一个地址空间。 DMA在硬件ROM和IO设备间开辟直接传输数据的通道不需要CPU主控芯片控制也不需要类似中断处理那种保留现场恢复现场的操作。这大大减小了CPU的负担。 2、使用场景 DMA用在只需要传输数据不需要处理数据的地方有三种传输方式
外设→存储器例从串口RDR寄存器写入某数据buf 存储器→外设例从某数据buf写入串口TDR寄存器 存储器→存储器例复制某特别大的数据buf
DMA相关的参数1 数据的源地址、2 数据传输的目标地址 、3 传输宽度4 传输多少字节5 传输模式。
传输宽度是指一次传输数据的的大小可以为字节8b、半字16b、字32b
传输模式分为正常模式一次结束和循环模式
DMA通道
STM32 最多有 2 个 DMA 控制器DMA2 仅存在大容量产品中 DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。每个通道都直接连接专用的硬件 DMA 请求每个通道都同样支持软件触发。这些功能通过软件来配置。 FIFO介绍 DMA接收有两种模式一种为直接模式另一种为FIFO模式 FIFO为缓存区大小为32位16个字节8个半字4个字独立的源和目标传输宽度字节、半字、字在单位上分为三种读取FIFO的方式为字节、半字、字每个数据流都有一个独立的 4 字 FIFO阈值级别可由软件配置为 1/4、1/2、3/4 或满。
二、代码编写
1.DMA
#include DMA.hu16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器-外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{DMA_InitTypeDef DMA_InitStructure;DMA1_MEM_LENcndtr;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值DMA_InitStructure.DMA_BufferSizecndtr;//DMA通道的DMA缓存的大小转运的数据量DMA_InitStructure.DMA_DIRDMA_DIR_PeripheralSRC;//数据传输方向从外设读取发送到内存DMA_InitStructure.DMA_M2MDMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输DMA_InitStructure.DMA_MemoryBaseAddrcmar;//DMA内存基地址DMA_InitStructure.DMA_MemoryDataSizeDMA_MemoryDataSize_Byte;//数据宽度为8位DMA_InitStructure.DMA_MemoryIncDMA_MemoryInc_Enable;//内存地址寄存器递增DMA_InitStructure.DMA_ModeDMA_Mode_Normal;//工作在正常模式DMA_InitStructure.DMA_PeripheralBaseAddrcpar;//DMA外设基地址DMA_InitStructure.DMA_PeripheralDataSizeDMA_PeripheralDataSize_Byte;//数据宽度为8位DMA_InitStructure.DMA_PeripheralIncDMA_PeripheralInc_Disable;//外设地址寄存器不变DMA_InitStructure.DMA_PriorityDMA_Priority_Medium; //DMA通道 x拥有中优先级 DMA_Init(DMA_CHx,DMA_InitStructure);USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);//使能外设的DMA通道这句可以放在对应的外设里
}//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ DMA_Cmd(DMA_CHx, DISABLE ); //关闭所指示的通道 DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//重新设定DMA通道的DMA缓存的大小DMA_Cmd(DMA_CHx, ENABLE); //使能所指示的通道
}
首先定义了全局变量DMA1_MEM_LEN用于保存每次数据传输的长度。然后使用MyDMA_Config函数配置DMA通道的参数包括DMA缓存大小、数据传输方向、内存地址寄存器递增等。其中cpar表示外设地址cmar表示存储器地址cndtr表示数据传输量。在配置完成后通过USART_DMACmd函数使能外设的DMA通道。
接着使用MYDMA_Enable函数开启一次DMA传输。该函数首先关闭所指示的DMA通道然后重新设定DMA缓存的大小并使能DMA通道。
#ifndef __DMA_H
#define __DMA_H#include sys.h#define rx_buff_maxlen 200//定义接受缓存区最长长度
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);
void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);#endif2.USART
#include sys.h
#include usart.h
#include DMA.h u8 len,Flag0;
//
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include includes.h //ucos 使用 #endif
//
//本程序只供学习使用未经作者许可不得用于其它任何用途
//ALIENTEK STM32开发板
//串口1初始化
//正点原子ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/8/18
//版本V1.5
//版权所有盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//V1.4修改说明
//1,修改串口初始化IO的bug
//2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
//3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改说明
//1,增加了对UCOSII的支持
// //
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{ int handle; }; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{ x x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{ while((USART1-SR0X40)0);//循环发送,直到发送完毕 USART1-DR (u8) ch; return ch;
}
#endif /*使用microLib的方法*//*
int fputc(int ch, FILE *f)
{USART_SendData(USART1, (uint8_t) ch);while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET) {} return ch;
}
int GetKey (void) { while (!(USART1-SR USART_FLAG_RXNE));return ((int)(USART1-DR 0x1FF));
}
*/
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx-SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15 接收完成标志
//bit14 接收到0x0d
//bit13~0 接收到的有效字节数目
u16 USART_RX_STA0; //接收状态标记 void uart_init(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1GPIOA时钟//USART1_TX GPIOA.9GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA, GPIO_InitStructure);//初始化GPIOA.9//USART1_RX GPIOA.10初始化GPIO_InitStructure.GPIO_Pin GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, GPIO_InitStructure);//初始化GPIOA.10 //Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority 3; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //IRQ通道使能NVIC_Init(NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//USART 初始化设置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(USART1, USART_InitStructure); //初始化串口1// USART_ITConfig(USART1, USART_IT_RXE, ENABLE);//开启串口接受中断USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //开启USART1的空闲中断USART_Cmd(USART1, ENABLE); //使能串口1 }void USART1_IRQHandler(void) //串口1中断服务程序当接受完毕后便会触发空闲中断{u8 clear0;if(USART_GetITStatus(USART1, USART_IT_IDLE) SET) //接收中断{clearUSART1-DR;//清楚中断标志位Flag1;//标志一次接受完毕在main函数中读取flag来判断是否接收完毕并在主函数中清零lenDMA_GetCurrDataCounter(DMA1_Channel5)-sizeof(rx_buff);//读取剩余未转运的长度}
}void MyUSART_SendByte(u8 Byte)
{USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET);//防止发送过快前一个数据还没发送出去就别覆盖的情况
}void MyUSART_SendString(char* str)//发送字符串
{u16 i;for(i0;str[i]!\0;i){MyUSART_SendByte(str[i]);}}
#endif
要注意的是这里开启的空闲中断 这里做中断标志位清0
#ifndef __USART_H
#define __USART_H
#include stdio.h
#include sys.h
#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1 //使能1/禁止0串口1接收extern u8 len;
extern u8 Flag;
extern char rx_buff[200];
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA; //接收状态标记
void uart_init(u32 bound);
#endif3.main
#include led.h
#include delay.h
#include key.h
#include sys.h
#include usart.h#include DMA.hint main(void){ u16 t; char rx_buff[200]{\0};u16 len; u16 times0;delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级2位响应优先级uart_init(115200); //串口初始化为115200LED_Init(); //LED端口初始化KEY_Init(); //初始化与按键连接的硬件接口MyDMA_Config(DMA1_Channel5,(u32)USART1-DR,(u32)rx_buff,rx_buff_maxlen);MYDMA_Enable(DMA1_Channel5);while(1){if(Flag){Flag0;MyUSART_SendString(rx_buff);MyUSART_SendString(\r\n);printf(len%d\r\n,len);MYDMA_Enable(DMA1_Channel5);}} }