记录一下学习过程
DMA
DMA,全称为: Direct Memory Access,即直接存储器访问, DMA 传输将数据从一个
地址空间复制到另外一个地址空间。 这一过程无需cpu的参与,从而提高cpu使用的效率
DMA相关的参数:1 数据的源地址、2 数据传输的目标地址 、3 传输宽度,4 传输多少字节,5 传输模式。
传输宽度是指一次传输数据的的大小,可以为字节(8b)、半字(16b)、字(32b)
传输模式分为正常模式(一次结束)和循环模式
DMA通道
STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5
个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
如图
DMA传输方式
外设到内存
内存到内存(配置为内存到内存时,DMA的模式只能选用正常模式)
内存到外设
DMA配置的代码以串口为例
#include "MyDMA.h"
u16 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_LEN=cndtr;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_BufferSize=cndtr;//DMA通道的DMA缓存的大小(转运的数据量)
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//数据传输方向,从外设读取发送到内存
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
DMA_InitStructure.DMA_MemoryBaseAddr=cmar;//DMA内存基地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度为8位
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址寄存器递增
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//工作在正常模式
DMA_InitStructure.DMA_PeripheralBaseAddr=cpar;//DMA外设基地址
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度为8位
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址寄存器不变
DMA_InitStructure.DMA_Priority=DMA_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); //使能所指示的通道
}
#ifndef __MYDMA_H
#define __MYDMA_H
#include "sys.h"
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);
void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);
#endif
DMA+串口空闲中断实现不定长收发
现在上面的DMA头文件部分定义接受缓存区最长长度
#ifndef __MYDMA_H
#define __MYDMA_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);
#endif
串口部分的配置
#include "MyUSART.h"
u8 len,Flag=0;
void MyUSART_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体
USART_InitTypeDef USART_InitStructure; //定义USART初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //定义NVIC初始化结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启USART1的时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //配置为复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; //PA9为TX
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIO初始化
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; //PA10为RX
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate=bound; //设置串口的波特率
USART_InitStructure.USART_WordLength=USART_WordLength_8b; //设置字长为8位数据格式
USART_InitStructure.USART_Mode=USART_Mode_Tx | USART_Mode_Rx; //设置串口模式为发送和接受
USART_InitStructure.USART_StopBits=USART_StopBits_1; //设置停止位为1位
USART_InitStructure.USART_Parity=USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; // 无硬件流控制
USART_Init(USART1,&USART_InitStructure); //初始化USART1
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断分组这个最好放在main函数里,确保所有中断都是采用同一分组
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;//设置中断来源
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //IRQ中断使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3; //设置响应优先级
NVIC_Init(&NVIC_InitStructure); //初始化NVIC
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //开启USART1的空闲中断
USART_Cmd(USART1,ENABLE); //使能USART1
}
void MyUSART_SendByte(u8 Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//防止发送过快,前一个数据还没发送出去就别覆盖的情况
}
void MyUSART_SendArray(u8* Array,u16 Length)//发送数组,最长长度为2^16
{
u16 i;
for(i=0;i<Length;i++)
{
MyUSART_SendByte(Array[i]);
}
}
void MyUSART_SendString(char* str)//发送字符串
{
u16 i;
for(i=0;str[i]!='\0';i++)
{
MyUSART_SendByte(str[i]);
}
}
void USART1_IRQHandler(void) //串口1中断服务程序,当接受完毕后便会触发空闲中断
{
u8 clear;
if(USART_GetITStatus(USART1, USART_IT_IDLE) == SET) //接收中断
{
clear=USART1->DR;//清楚中断标志位
Flag=1;//标志一次接受完毕,在main函数中读取flag来判断是否接收完毕,并在主函数中清零
len=DMA_GetCurrDataCounter(DMA1_Channel5)-sizeof(rx_buff);//读取剩余未转运的长度
}
}
头文件部分
#ifndef __MYUSART_H
#define __MYUSART_H
#include "sys.h"
#include "OLED.h"
#include "Delay.h"
#include "MyDMA.h"
extern u8 len;
extern u8 Flag;
extern char rx_buff[200];
void MyUSART_Init(u32 bound);
extern u8 ReceiveData ;
void MyUSART_SendByte(u8 Byte);
void MyUSART_SendArray(u8* Array,u16 Length);
void MyUSART_SendString(char* str);
#endif
main函数部分
#include "MyUSART.h" // Device header
int main()
{
char rx_buff[200]={'\0'};
MyUSART_Init(115200);
OLED_Init();
MyDMA_Config(DMA1_Channel5,(u32)&USART1->DR,(u32)rx_buff,rx_buff_maxlen);
MYDMA_Enable(DMA1_Channel5);
while(1)
{
if(Flag)
{
Flag=0;
MyUSART_SendString(rx_buff);
MyUSART_SendString("\r\n");
OLED_ShowNum(1,1,len,3);
MYDMA_Enable(DMA1_Channel5);
}
}
}