STM32F4串口1使用DMA收发数据
一、什么是DMA?
直接存储器访问(DMA)用于在外设与存储器之间以及存储器与存储器与存储器之间提供数据传输。它无需CPU参与而自动移动数据,即DMA就是一个数据搬运工。
二、怎么使用DMA?
既然DMA是一个数据搬运工,那么根据数据传输三要素,必须知道数据的源、目的和长度。在STM32F4中还需要设置数据传输方向等其它项,具体参考代码。
三、STM32F4的DMA简介
STM32F4有2个DMA,每个DMA控制器有8个数据流,每个数据流有多达8个通道,但是仅DMA2数据流能够执行存储器到存储器之间的数据传输。其中DMA2的请求映射如下:
从图中可以看出串口1的接收和发送分别位于数据流2的通道4和数据流7的通道4。
四、代码解析
函数的功能很简单,就是将串口接收到的数据然后发送出去。
1.main函数
int main(void)
{
/* 时钟初始化 */
Stm32f4xxClockInit();
/* 串口1初始化 */
UART1Init();
/* 使用DMA发送数据初始化 */
DMA_Use_USART1_Tx_Init();
/* 使用DMA接收数据初始化 */
DMA_Use_USART1_Rx_Init();
/* 打开总中断 */
__enable_irq();
for(;;)
{
if(tUART1_Rx.dwUART1RxLen && UART1_Use_DMA_Tx_Flag == 0)
{
Use_DMA_tx(tUART1_Rx.UART1RxBuf, tUART1_Rx.dwUART1RxLen);
tUART1_Rx.dwUART1RxLen = 0;
}
}
}
2.串口1初始化
串口的初始化注意中断的配置,要使用DMA接收数据,必须使能IDLE中断,因为在数据的传输三要素中,使用DMA接收数据不知道数据的长度,故必须使能IDLE中断来判断该次数据是否接收完成。
/* 库版本: V1.8.0
* 串口1初始化
* 使用PA9和PA10
*/
static void UART1Init(void)
{
USART_DeInit(USART1);
/* 1.使能PA口时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/* 2.使能串口1时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/* 3.管脚映射 */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); /* GPIOA9复用为USART1 */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); /* GPIOA10复用为USART1 */
/* 4.配置串口1的PA9和PA10管脚 */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure); /* TXIO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure); /* RXIO */
/* 5.配置串口工作模式 */
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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);
/* 6.设置中断 */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 7.配置串口中断 */
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
USART_ITConfig(USART1, USART_IT_TC, DISABLE);
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);/* 使能IDLE中断 */
USART_Cmd(USART1, ENABLE);
}
3.DMA发送数据初始化
在配置DMA发送数据时,只有当要发送数据时才使能DMA,因为使能DMA后,数据就开始传输了。直到数据传输完成进入DMA中断。
static void DMA_Use_USART1_Tx_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/* 1.使能DMA2时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
/* 2.配置使用DMA发送数据 */
DMA_DeInit(DMA2_Stream7);
DMA_InitStructure.DMA_Channel = DMA_Channel_4; /* 配置DMA通道 */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART1->DR)); /* 目的 */
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Tx_Buf; /* 源 */
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; /* 方向 */
DMA_InitStructure.DMA_BufferSize = TX_BUF_LEN; /* 长度 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /* 外设地址是否自增 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* 内存地址是否自增 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; /* 目的数据带宽 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /* 源数据宽度 */
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /* 单次传输模式/循环传输模式 */
DMA_InitStructure.DMA_Priority = DMA_Priority_High; /* DMA优先级 */
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; /* FIFO模式/直接模式 */
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; /* FIFO大小 */
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; /* 单次传输 */
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
/* 3. 配置DMA */
DMA_Init(DMA2_Stream7, &DMA_InitStructure);
/* 4.使能DMA中断 */
DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE);
/* 5.使能串口的DMA发送接口 */
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
/* 6. 配置DMA中断优先级 */
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 7.不使能DMA */
DMA_Cmd(DMA2_Stream7, DISABLE);
}
4. DMA接收数据初始化
在串口使用DMA接收时,由于不知道数据传输的长度,故不能使能DMA接收中断。
static void DMA_Use_USART1_Rx_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
tUART1_Rx.dwUART1RxLen = 0;
/* 1.使能DMA2时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
/* 2.配置使用DMA接收数据 */
DMA_DeInit(DMA2_Stream2);
DMA_InitStructure.DMA_Channel = DMA_Channel_4; /* 配置DMA通道 */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART1->DR)); /* 源 */
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Rx_Buf; /* 目的 */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; /* 方向 */
DMA_InitStructure.DMA_BufferSize = RX_BUF_LEN; /* 长度 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /* 外设地址是否自增 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* 内存地址是否自增 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; /* 目的数据带宽 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /* 源数据宽度 */
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /* 单次传输模式/循环传输模式 */
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; /* DMA优先级 */
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; /* FIFO模式/直接模式 */
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; /* FIFO大小 */
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; /* 单次传输 */
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
/* 3. 配置DMA */
DMA_Init(DMA2_Stream2, &DMA_InitStructure);
/* 4.由于接收不需要DMA中断,故不设置DMA中断 */
/* 5.使能串口的DMA接收 */
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
/* 6. 由于接收不需要DMA中断,故不能配置DMA中断优先级 */
/* 7.使能DMA */
DMA_Cmd(DMA2_Stream2,ENABLE);
}
5.中断解析
当串口接收数据完成后,就会进入中断,在接收中断中需要清除IDLE中断和DMA接收中断。
void USART1_IRQFuc(void)
{
/* 发送完成中断处理 */
deal_irq_tx_end();
/* 接收完成中断处理 */
tUART1_Rx.dwUART1RxLen = deal_irq_rx_end(tUART1_Rx.UART1RxBuf);
}
5.1 接收中断解析
在串口接收完成中断中最终会调用deal_irq_rx_end函数,在该函数中清除中断标识,并将接收到的数据拷贝到tUART1_Rx.UART1RxBuf中。
static uint8_t deal_irq_rx_end(uint8_t *buf)
{
uint16_t len = 0;
/* 接收完成中断 */
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
USART1->SR;
USART1->DR; /* 清USART_IT_IDLE标志 */
/* 关闭接收DMA */
DMA_Cmd(DMA2_Stream2,DISABLE);
/* 清除标志位 */
DMA_ClearFlag(DMA2_Stream2,DMA_FLAG_TCIF2);
/* 获得接收帧帧长 */
len = RX_BUF_LEN - DMA_GetCurrDataCounter(DMA2_Stream2);
memcpy(buf,Rx_Buf,len);
/* 设置传输数据长度 */
DMA_SetCurrDataCounter(DMA2_Stream2,RX_BUF_LEN);
/* 打开DMA */
DMA_Cmd(DMA2_Stream2,ENABLE);
return len;
}
return 0;
}
5.2发送数据解析
当接收到数据时,并且发送DMA空闲时,主函数中就会启动DMA发送数据。
for(;;)
{
if(tUART1_Rx.dwUART1RxLen && UART1_Use_DMA_Tx_Flag == 0)
{
Use_DMA_tx(tUART1_Rx.UART1RxBuf, tUART1_Rx.dwUART1RxLen);
tUART1_Rx.dwUART1RxLen = 0;
}
}
使用函数 Use_DMA_tx 启动DMA发送数据。
static void Use_DMA_tx(uint8_t *data,uint16_t size)
{
/* 等待空闲 */
while (UART1_Use_DMA_Tx_Flag);
UART1_Use_DMA_Tx_Flag = 1;
/* 复制数据 */
memcpy(Tx_Buf,data,size);
/* 设置传输数据长度 */
DMA_SetCurrDataCounter(DMA2_Stream7,size);
/* 打开DMA,开始发送 */
DMA_Cmd(DMA2_Stream7,ENABLE);
}
5.3发送完成DMA中断解析
在发送DMA中断中,主要是清除DMA中断标识,关闭DMA传输。
void DMA2_Stream7_IRQFuc(void)
{
if(DMA_GetITStatus(DMA2_Stream7,DMA_IT_TCIF7) != RESET)
{
/* 清除标志位 */
DMA_ClearFlag(DMA2_Stream7,DMA_IT_TCIF7);
/* 关闭DMA */
DMA_Cmd(DMA2_Stream7,DISABLE);
/* 打开发送完成中断,确保最后一个字节发送成功 */
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
}
}
在DMA发送完成中断中,为了确保传输的最后一个字节发送成功,必须启动串口发送中断,确保发送数据的完整。因此才有了串口中断中的发送完成中断处理函数deal_irq_tx_end,在该函数中清除发送完成中断,清除DMA发送忙标识。
static void deal_irq_tx_end(void)
{
if(USART_GetITStatus(USART1, USART_IT_TXE) == RESET)
{
/* 关闭发送完成中断 */
USART_ITConfig(USART1,USART_IT_TC,DISABLE);
/* 发送完成 */
UART1_Use_DMA_Tx_Flag = 0;
}
}