一、实现功能
串口通过DMA通道打印回来我们发送过去的数据
二、DMA通道映射表
我们要用的是DMA2的channel4的stream5和stream7
三、通道分布
通道4既可以通过stream5也可以通过stream7,而stream5即为USART1_RX的流,stream7即为USART7_TX的流。
四、部分关键函数
四、记录
(1)使能控制必须得在初始化之后。
(2)DMA发送时,方向为内存到外设。DMA接收时,方向相反。
(3)注意优先级
(4)注意外设地址以及内存地址
(5)不使用FIFO
(6)单次burst触发
(7)普通模式
(8)内存地址自增(存入数组)
(9)单位数据单元为一个字节byte
(10)发送数据时需要先获取接收到的数据单元个数,方便设置放松通道的数据单元个数
(11)发送设置完成之后,需要重新设置接收的数据单元空间(免得堆满)
(12)再准备下一次接受之前记得先把接收完成标志位清0(最好一进入中断就清0)
(13)DMA发送通道使能是在需要发送时才调用的,其他时刻处于失能状态。
五、逻辑
串口配置空闲中断,DMA2的stream5和stream7配置中断(stream5可以不配置),主要涉及到一个需要清除标志位,什么时候清除标志位。串口设置了空闲中断,接收完成之后会产生空闲中断,此时就可以将其标志位DMA接收标志位清除。空闲中断之后马上又会DMA发送数据,前面的stream7的中断配置为(传输完成中断),完成之后进入传输完成中断将标志位清0即可,同时失能(等待下一次接收数据完成再使能DMA发送)
六、代码
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include "board.h"
#include "bsp_uart.h"
#include "stdio.h"
#include "string.h"
#include "sys.h"
#define USART_MAX_LEN 400
volatile uint16_t usart1_rx_len = 0; //接收帧数据的长度
volatile uint16_t usart1_tx_len = 0; //发送帧数据的长度
volatile uint8_t usart1_recv_end_flag = 0;//帧数据接收完成标志
uint8_t DMA_USART1_RX_BUF[USART_MAX_LEN]={0}; //接收数据缓存
uint8_t DMA_USART1_TX_BUF[USART_MAX_LEN]={0}; //DMA发送缓存
void DMA_USART1_Send(u8 *data,u16 size);
int main(void)
{
board_init();
//1.定义结构体
GPIO_InitTypeDef gpioinit;
//2.开启gpio时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//3.复用配置(注意此处不可以用 | )
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//4.配置gpio
gpioinit.GPIO_Mode=GPIO_Mode_AF;
gpioinit.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;
gpioinit.GPIO_OType=GPIO_OType_PP;
gpioinit.GPIO_PuPd=GPIO_PuPd_UP;
gpioinit.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&gpioinit);
//5.定义串口结构体
USART_InitTypeDef usart1_init;
//6.开启串口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//7.配置串口
usart1_init.USART_BaudRate=115200U;
usart1_init.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
usart1_init.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
usart1_init.USART_Parity=USART_Parity_No;
usart1_init.USART_StopBits=USART_StopBits_1;
usart1_init.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&usart1_init);
//8.初始化成功清除接收置位
USART_ClearFlag(USART1,USART_FLAG_RXNE);
//9.使能串口
USART_Cmd(USART1,ENABLE);
//10.定义中断结构体
NVIC_InitTypeDef nvic_init;
//11.配置串口中断
nvic_init.NVIC_IRQChannel=USART1_IRQn;
nvic_init.NVIC_IRQChannelCmd=ENABLE;
nvic_init.NVIC_IRQChannelPreemptionPriority=0x00;
nvic_init.NVIC_IRQChannelSubPriority=0x01;
NVIC_Init(&nvic_init);
//12.配置DMA接收中断(RX)
nvic_init.NVIC_IRQChannel=DMA2_Stream5_IRQn;
nvic_init.NVIC_IRQChannelCmd=ENABLE;
nvic_init.NVIC_IRQChannelPreemptionPriority=0x01;
nvic_init.NVIC_IRQChannelSubPriority=0x01;
NVIC_Init(&nvic_init);
//13.配置DMA发送中断(TX)
nvic_init.NVIC_IRQChannel=DMA2_Stream7_IRQn;
nvic_init.NVIC_IRQChannelCmd=ENABLE;
nvic_init.NVIC_IRQChannelPreemptionPriority=0x01;
nvic_init.NVIC_IRQChannelSubPriority=0x02;
NVIC_Init(&nvic_init);
//14.允许空闲中断,允许DMA接收发送
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
//15.配置DMA2串口1接收RX
DMA_InitTypeDef DMA_InitStucture;
//16.开DMA2时钟 CHANNEL4 STREAM5
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);
DMA_DeInit(DMA2_Stream5);
DMA_InitStucture.DMA_BufferSize=USART_MAX_LEN; //设置DMA的最大数据单位个数
DMA_InitStucture.DMA_Channel=DMA_Channel_4; //通道4
DMA_InitStucture.DMA_DIR=DMA_DIR_PeripheralToMemory; //外设到存储器
DMA_InitStucture.DMA_FIFOMode=DMA_FIFOMode_Disable; //禁止FIFO模式
DMA_InitStucture.DMA_FIFOThreshold=DMA_FIFOThreshold_Full; //fifo阈值
DMA_InitStucture.DMA_Memory0BaseAddr=(uint32_t)DMA_USART1_RX_BUF; //DMA存储器基地址
DMA_InitStucture.DMA_MemoryBurst=DMA_MemoryBurst_Single; //内存地址递增步进
DMA_InitStucture.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; //内存数据单位:一个字节
DMA_InitStucture.DMA_MemoryInc=DMA_MemoryInc_Enable; //允许内存空间地址自增
DMA_InitStucture.DMA_Mode=DMA_Mode_Normal; //普通模式
DMA_InitStucture.DMA_PeripheralBaseAddr=(uint32_t)&USART1->DR; //外设基地址
DMA_InitStucture.DMA_PeripheralBurst=DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_InitStucture.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte; //外设数据单位: 一个字节
DMA_InitStucture.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //不允许外设地址自增
DMA_InitStucture.DMA_Priority=DMA_Priority_High; //DMA高优先级
DMA_Init(DMA2_Stream5,&DMA_InitStucture);
DMA_Cmd(DMA2_Stream5,ENABLE); //使能SMA2的Stream5
DMA_DeInit(DMA2_Stream7); //初始化DMA Stream
while(DMA_GetCmdStatus(DMA2_Stream7) != DISABLE);//等待DMA可配置
//17.配置DMA2串口1发送TX
DMA_InitStucture.DMA_BufferSize=USART_MAX_LEN;
DMA_InitStucture.DMA_Channel=DMA_Channel_4; //通道4
DMA_InitStucture.DMA_DIR=DMA_DIR_MemoryToPeripheral; //存储器到外设
DMA_InitStucture.DMA_FIFOMode=DMA_FIFOMode_Disable; //禁止FIFO模式
DMA_InitStucture.DMA_FIFOThreshold=DMA_FIFOThreshold_1QuarterFull; //fifo阈值
DMA_InitStucture.DMA_Memory0BaseAddr=(uint32_t)DMA_USART1_TX_BUF; //DMA存储器基地址
DMA_InitStucture.DMA_MemoryBurst=DMA_MemoryBurst_Single; //内存地址递增步进
DMA_InitStucture.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; //内存数据单位:一个字节
DMA_InitStucture.DMA_MemoryInc=DMA_MemoryInc_Enable; //允许内存空间地址自增
DMA_InitStucture.DMA_Mode=DMA_Mode_Normal; //普通模式
DMA_InitStucture.DMA_PeripheralBaseAddr=(uint32_t)&USART1->DR; //外设基地址
DMA_InitStucture.DMA_PeripheralBurst=DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_InitStucture.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte; //外设数据单位: 一个字节
DMA_InitStucture.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //不允许外设地址自增
DMA_InitStucture.DMA_Priority=DMA_Priority_Medium; //DMA中优先级
DMA_Init(DMA2_Stream7,&DMA_InitStucture);
DMA_Cmd(DMA2_Stream7,DISABLE); //先失能DMA发送,在需要使用时再使能
//18.配置DMA发送完成后中断
DMA_ITConfig(DMA2_Stream7,DMA_IT_TC,ENABLE); //DMA发送完成中断
DMA_USART1_Send((uint8_t *)"hello world!!\r\n",13);
while(1)
{
}
}
//串口1中断服务函数
void USART1_IRQHandler(void)
{
//19.1 DMA接收完成,进入空闲
if(USART_GetITStatus(USART1,USART_IT_IDLE)==SET)
{
//19.2 失能DMA接收
DMA_Cmd(DMA2_Stream5,DISABLE);
//19.3 获得收到的数据的长度
usart1_rx_len=USART_MAX_LEN-DMA_GetCurrDataCounter(DMA2_Stream5); //DMA_GetCurrDataCounter函数是获取剩余空间
//19.4 清除DMA接收完成标志位
DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5);
/*
19.5到19.7可以替换为DMA_USART1_Send(DMA_USART1_RX_BUF,usart1_rx_len);
*/
//19.5 往发送buff里面转入接收到的数据
memcpy(DMA_USART1_TX_BUF,DMA_USART1_RX_BUF,usart1_rx_len);
while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE); //确保DMA可以被设置
//19.6 设置DMA发送的buff的长度
DMA_SetCurrDataCounter(DMA2_Stream7,usart1_rx_len);
//19.7 使能DMA发送(这是DMA开始自动发送)
DMA_Cmd(DMA2_Stream7,ENABLE);
//19.8 重新设置接收DMA的长度
DMA_SetCurrDataCounter(DMA2_Stream5,USART_MAX_LEN);
//19.9 重新使能DMA接收
DMA_Cmd(DMA2_Stream5,ENABLE);
//19.10 清除空闲中断
USART_ReceiveData(USART1); //清除空闲中断标志位(接收函数有清标志位的作用)
}
}
//发送完成中断
void DMA2_Stream7_IRQHandler(void)
{
//20.1 DMA发送完毕
if(DMA_GetITStatus(DMA2_Stream7,DMA_IT_TCIF7))
{
//20.2 清除DMA发送完成标志位
DMA_ClearITPendingBit(DMA2_Stream7,DMA_IT_TCIF7);
//20.3 失能DMA发送
DMA_Cmd(DMA2_Stream7,DISABLE); //失能DMA发送
}
}
//DMA发送函数
void DMA_USART1_Send(u8 *data,u16 size)
{
memcpy(DMA_USART1_TX_BUF,data,size); //复制数据到DMA发送缓存区
while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE); //确保DMA可以被设置
DMA_SetCurrDataCounter(DMA2_Stream7,size); //设置数据传输长度
DMA_Cmd(DMA2_Stream7,ENABLE); //打开DMA数据流,开始发送
}