1.背景简介
没有使用DMA的USART,我们在前面已经讲过了。【STM32】USART串口通讯-CSDN博客。现在在该基础上我们加上DMA功能,仅使用DMA进行发送。
2.DMA_TX源码
c
#ifndef __BSP_USART_H
#define __BSP_USART_H
#ifdef __cplusplus
extern "C"{
#endif
#include "stm32f4xx.h"
#include "stdio.h"
#define LOGGER_USART USART1
#define LOGGER_USART_BAUDRATE 115200
#define LOGGER_USART_CLK RCC_APB2Periph_USART1
#define LOGGER_USART_IRQHandler USART1_IRQHandler
#define LOGGER_USART_IRQ USART1_IRQn
#define USART1_TX_PIN GPIO_Pin_9
#define USART1_TX_GPIO_Port GPIOA
#define USART1_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define USART1_TX_AF GPIO_AF_USART1
#define USART1_TX_SOURCE GPIO_PinSource9
#define USART1_RX_PIN GPIO_Pin_10
#define USART1_RX_GPIO_Port GPIOA
#define USART1_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define USART1_RX_AF GPIO_AF_USART1
#define USART1_RX_SOURCE GPIO_PinSource10
//usart1_tx只能使用到DMA2_Stream7 Channel_4
#define USART1_TX_DMA_STREAM DMA2_Stream7
#define USART1_TX_DMA_CHANNEL DMA_Channel_4
#define USART1_TX_DMA_STREAM_CLK RCC_AHB1Periph_DMA2
#define USART1_TX_DMA_IT_TCIF DMA_IT_TCIF7
#define USART1_TX_DMA_IT_HTIF DMA_IT_HTIF7
#define USART1_TX_DMA_FLAG_TCIF DMA_FLAG_TCIF7
#define USART1_TX_DMA_FLAG_HTIF DMA_FLAG_HTIF7
#define USART1_TX_DMA_STREAM_IRQn DMA2_Stream7_IRQn
#define USART1_TX_DMA_STREAM_IRQHandler DMA2_Stream7_IRQHandler
#define TX_BUFFER_SIZE 32
#define USART1_TX_DR_BASE (USART1_BASE+0x04)
void Init_USART(void);
void Init_USART_DMA(void);
void USART_DMA_SEND(uint8_t* data,uint32_t size);
#ifdef __cplusplus
}
#endif
#endif
c
#include "bsp_usart.h"
#include "string.h"
void Init_USART(void)
{
RCC_AHB1PeriphClockCmd(USART1_TX_GPIO_CLK|USART1_RX_GPIO_CLK,ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(LOGGER_USART_CLK,ENABLE);//使能USART1时钟
//USART1对应引脚复用映射
GPIO_PinAFConfig(USART1_TX_GPIO_Port, USART1_TX_SOURCE,USART1_TX_AF);//PA9复用为USART1
GPIO_PinAFConfig(USART1_RX_GPIO_Port, USART1_RX_SOURCE,USART1_RX_AF);//PA10复用为USART1
//USART1端口配置
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin=USART1_TX_PIN;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//复用功能
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽复用输出
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;//速度50MHz
GPIO_Init(USART1_TX_GPIO_Port,&GPIO_InitStruct);//初始化PA9
GPIO_InitStruct.GPIO_Pin=USART1_RX_PIN;
GPIO_Init(USART1_RX_GPIO_Port,&GPIO_InitStruct);//初始化PA10
//配置USART参数
USART_InitTypeDef USART_Init_Struct;
USART_Init_Struct.USART_BaudRate=LOGGER_USART_BAUDRATE;
USART_Init_Struct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_Init_Struct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_Init_Struct.USART_Parity=USART_Parity_No;
USART_Init_Struct.USART_StopBits=USART_StopBits_1;
USART_Init_Struct.USART_WordLength=USART_WordLength_8b;
USART_Init(LOGGER_USART,&USART_Init_Struct);
//配置中断控制器并使能USART接收中断
NVIC_InitTypeDef NVIC_Init_Struct;
NVIC_Init_Struct.NVIC_IRQChannel=LOGGER_USART_IRQ;
NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_Init_Struct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_Init_Struct);
USART_ITConfig(LOGGER_USART,USART_IT_RXNE,ENABLE);
//使能USART
USART_Cmd(LOGGER_USART,ENABLE);
//使能USART_DMA
USART_DMACmd(LOGGER_USART,USART_DMAReq_Tx,ENABLE);
}
uint8_t USART_TX_BUFFER[TX_BUFFER_SIZE]={0};
void USART_DMA_SEND(uint8_t* data,uint32_t size)
{
while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
}
memcpy(USART_TX_BUFFER,data,size);
DMA_Cmd(USART1_TX_DMA_STREAM,DISABLE);
DMA_SetCurrDataCounter(USART1_TX_DMA_STREAM,size);
DMA_Cmd(USART1_TX_DMA_STREAM,ENABLE);
}
void Init_USART_DMA(void)
{
/* 使能DMA时钟 */
RCC_AHB1PeriphClockCmd(USART1_TX_DMA_STREAM_CLK, ENABLE);
/* 复位初始化DMA数据流 */
DMA_DeInit(USART1_TX_DMA_STREAM);
/* 确保DMA数据流复位完成 */
while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
}
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_BufferSize=TX_BUFFER_SIZE;//一次DMA事务传输的数据个数
DMA_InitStructure.DMA_Channel=USART1_TX_DMA_CHANNEL;
DMA_InitStructure.DMA_DIR=DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)USART_TX_BUFFER;
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_TX_DR_BASE;
DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_Priority=DMA_Priority_Low;
DMA_Init(USART1_TX_DMA_STREAM,&DMA_InitStructure);
DMA_ITConfig(USART1_TX_DMA_STREAM,DMA_IT_TC|DMA_IT_HT,ENABLE);
DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
//配置中断控制器并使能中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=USART1_TX_DMA_STREAM_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
//这里没有使能DMA,而是在给buffer赋值后再使能
}
void USART1_TX_DMA_STREAM_IRQHandler(void)
{
if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF))
{
//half transfer complete
//printf("half transfer\r\n");
DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF);
}
else if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF))
{
//transfer complete
//printf("transfer complete\r\n");
DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
}
}
总结:
- 这里我们固定了存储器地址,然后需要串口发送的数据都拷贝到这个地址上。DMA模式也是正常模式,所以发送完了后会自动结束。
- 我们可以固定DMA发送长度,如果想每次发送实际的长度,我们可以进行修改,DMA_SetCurrDataCounter。但是需要在设置DMA DISABLE(DMA_Cmd函数)的时候才可以配置。
- 在DMA初始化的时候,我们没有使能(DMA_Cmd函数),而是在配置好数据后使能的。
- 每一次发送数据的时候,需要确认DMA的状态是否还在发送数据。因为可能上一次还在发送数据时,你再次调用发送函数了。
- 外设的地址,我们需要查看源码。串口需要查看其结构体定义,其中Data register就是。或者用这种方式也行
#define USART1_TX_DR_BASE (&USART1->DR) //(USART1_BASE+0x04)
。 - 配置USART的时候,需要配置USART DMA使能(有TX和RX)。
3.DMA_RX源码
先说一下思路,配置DMA_RX的时候应该是收一个固定的长度,因为你没有办法知道发送的长度是多少。USART_DMA_RX的工作原理大概理解为,当有数据到USART->DR的时候,会触发RXNE事件,这个时候DMA会自动将DR的数据搬运到存储器,否则就等待。所以,我们就在USART_IT_RXNE中断中去读取存储器的数据吗?
个人理解是不行的,实际实验结果也是不行的。因为RXNE中断产生的时候,DMA才开始搬运DR到存储器。
那我们应该在DMA_IT_TC中断服务函数中去读存储器的值吧?其实也不行,因为大概率串口接收的数据不会等于USART_DMA_RX设置的长度,所以不会进入该中断。
我们应该在USART_IT_IDLE中断服务函数中去读取存储器的值。
串口的空闲中断:检测到空闲线路的时候会产生中断,那么何为空闲线路?我们发一堆数据过去,连续的两个数据中肯定有时间间隔,很明显在这段时间内并不叫空闲;空闲中断其实也叫帧中断,即****在总线上在一个字节的时间内没有再次接收到数据****,那么此时就会产生中断,即空闲中断。
然后,在串口空闲中断函数中,我们读到存储器数据后,其实USART_DMA_RX的接收事务还未完成,因为它还没有收够足够的长度,如果下次还有数据,那么就是继续往存储器中写入。但是这样就会造成前一次接收的数据还存在,所以,我们得恢复USART_DMA_RX的接收长度为原大小。然后最好得设置为循环模式,这样我们就不用管收满后DMA重启的情况。
c
#include "bsp_usart.h"
#include "string.h"
uint8_t USART_TX_BUFFER[TX_BUFFER_SIZE]={0};
uint8_t USART_RX_BUFFER[RX_BUFFER_SIZE]={0};
void Init_USART(void)
{
RCC_AHB1PeriphClockCmd(USART1_TX_GPIO_CLK|USART1_RX_GPIO_CLK,ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(LOGGER_USART_CLK,ENABLE);//使能USART1时钟
//USART1对应引脚复用映射
GPIO_PinAFConfig(USART1_TX_GPIO_Port, USART1_TX_SOURCE,USART1_TX_AF);//PA9复用为USART1
GPIO_PinAFConfig(USART1_RX_GPIO_Port, USART1_RX_SOURCE,USART1_RX_AF);//PA10复用为USART1
//USART1端口配置
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin=USART1_TX_PIN;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//复用功能
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽复用输出
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;//速度50MHz
GPIO_Init(USART1_TX_GPIO_Port,&GPIO_InitStruct);//初始化PA9
GPIO_InitStruct.GPIO_Pin=USART1_RX_PIN;
GPIO_Init(USART1_RX_GPIO_Port,&GPIO_InitStruct);//初始化PA10
//配置USART参数
USART_InitTypeDef USART_Init_Struct;
USART_Init_Struct.USART_BaudRate=LOGGER_USART_BAUDRATE;
USART_Init_Struct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_Init_Struct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_Init_Struct.USART_Parity=USART_Parity_No;
USART_Init_Struct.USART_StopBits=USART_StopBits_1;
USART_Init_Struct.USART_WordLength=USART_WordLength_8b;
USART_Init(LOGGER_USART,&USART_Init_Struct);
//配置中断控制器并使能USART接收中断
NVIC_InitTypeDef NVIC_Init_Struct;
NVIC_Init_Struct.NVIC_IRQChannel=LOGGER_USART_IRQ;
NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_Init_Struct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_Init_Struct);
USART_ITConfig(LOGGER_USART,USART_IT_IDLE,ENABLE);
//使能USART
USART_Cmd(LOGGER_USART,ENABLE);
//使能USART_DMA
USART_DMACmd(LOGGER_USART,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
}
void LOGGER_USART_IRQHandler(void)
{
if(SET==USART_GetITStatus(LOGGER_USART,USART_IT_IDLE))
{
//我们配置DMA_RX的长度是32,
//但是如果收到的实际数据较短,也可以收到,这里也会触发,但是DMA_RX应该还在等待中,没有结束
//下次如果还有数据来的话,DMA会继续收,还是会存放在RX_BUFFER,但是不是从首地址。知道收到的数据长度为32了,才会结束这一次的DMA_RX事务。
USART_DMA_SEND(USART_RX_BUFFER,RX_BUFFER_SIZE);
DMA_Cmd(USART1_RX_DMA_STREAM,DISABLE);
DMA_SetCurrDataCounter(USART1_RX_DMA_STREAM,RX_BUFFER_SIZE);
DMA_Cmd(USART1_RX_DMA_STREAM,ENABLE);
USART_ReceiveData(LOGGER_USART);//清除USART_IT_IDLE中断标志位
}
}
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(LOGGER_USART, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(LOGGER_USART, USART_FLAG_TXE) == RESET);
return (ch);
}
void USART_DMA_SEND(uint8_t* data,uint32_t size)
{
while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
}
memcpy(USART_TX_BUFFER,data,size);
DMA_Cmd(USART1_TX_DMA_STREAM,DISABLE);
DMA_SetCurrDataCounter(USART1_TX_DMA_STREAM,size);
DMA_Cmd(USART1_TX_DMA_STREAM,ENABLE);
}
void Init_USART_DMA(void)
{
/* 使能DMA时钟 */
RCC_AHB1PeriphClockCmd(USART1_TX_DMA_STREAM_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(USART1_RX_DMA_STREAM_CLK, ENABLE);
/* 复位初始化DMA数据流 */
DMA_DeInit(USART1_TX_DMA_STREAM);
/* 确保DMA数据流复位完成 */
while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
}
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_BufferSize=TX_BUFFER_SIZE;//一次DMA事务传输的数据个数
DMA_InitStructure.DMA_Channel=USART1_TX_DMA_CHANNEL;
DMA_InitStructure.DMA_DIR=DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)USART_TX_BUFFER;
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_TX_DR_BASE;
DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_Priority=DMA_Priority_Low;
DMA_Init(USART1_TX_DMA_STREAM,&DMA_InitStructure);
DMA_ITConfig(USART1_TX_DMA_STREAM,DMA_IT_TC|DMA_IT_HT,ENABLE);
DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
//配置中断控制器并使能中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=USART1_TX_DMA_STREAM_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
//这里没有使能DMA,而是在给buffer赋值后再使能
/* 复位初始化DMA数据流 */
DMA_DeInit(USART1_RX_DMA_STREAM);
/* 确保DMA数据流复位完成 */
while (DMA_GetCmdStatus(USART1_RX_DMA_STREAM) != DISABLE) {
}
DMA_InitTypeDef RX_DMA_InitStructure;
RX_DMA_InitStructure.DMA_BufferSize=RX_BUFFER_SIZE;//一次DMA事务传输的数据个数
RX_DMA_InitStructure.DMA_Channel=USART1_RX_DMA_CHANNEL;
RX_DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory;
RX_DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
RX_DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;
RX_DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)USART_RX_BUFFER;
RX_DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
RX_DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
RX_DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
RX_DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
RX_DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_RX_DR_BASE;
RX_DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
RX_DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
RX_DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
RX_DMA_InitStructure.DMA_Priority=DMA_Priority_Low;
DMA_Init(USART1_RX_DMA_STREAM,&RX_DMA_InitStructure);
// DMA_ITConfig(USART1_RX_DMA_STREAM,DMA_IT_TC|DMA_IT_HT,ENABLE);
DMA_ClearITPendingBit(USART1_RX_DMA_STREAM,USART1_RX_DMA_IT_TCIF);
//配置中断控制器并使能中断
NVIC_InitTypeDef RX_NVIC_InitStruct;
RX_NVIC_InitStruct.NVIC_IRQChannel=USART1_RX_DMA_STREAM_IRQn;
RX_NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
RX_NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
RX_NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&RX_NVIC_InitStruct);
DMA_Cmd(USART1_RX_DMA_STREAM,ENABLE);
}
void USART1_TX_DMA_STREAM_IRQHandler(void)
{
if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF))
{
//half transfer complete
//printf("half transfer\r\n");
DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF);
}
else if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF))
{
//transfer complete
//printf("transfer complete\r\n");
DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
}
}
还有一点:
- 关于清除USART_IT_IDLE中断标志,可以看源码注释,不能使用常规的函数clear。