DMA+串口收发数据
1、DMA+串口发送数据
当串口的波特率大于115200时,可以通过DMA1进行数据搬运,以防止数据的丢失。如上图所示:UART1的Tx发送请求使用DMA1的通道4,UART1的Rx接收数据请求使用DMA1的通道5。
①串口发送时 :当UART1的发送数据寄存器TDR中没有数据时,就会向DMA1的通道4申请数据搬运,DMA1将缓冲区的数据搬运到TDR数据寄存器中,然后串口将数据发送出去。
②串口接收时 :当UART1的接收数据寄存器RDR中有数据时,就会向DMA1的通道5申请数据搬运,DMA1将数据从RDR寄存器中搬运到缓冲区中。
【注意】数据的搬运和数据的发送的过程都不需要CPU参与,CPU只参与串口UART1和DMA1通道1的配置。
①UART.c文件的代码如下:
c
#include "UART.h"
uint8_t Buff[Buffer_Size];//定义数据缓冲区
/**
* 串口1的初始化函数
*/
void UART1_Init(void)
{
/* 开启串口的UART1的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/* 开启串口的GPIO的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 配置串口1的引脚 */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;// 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置串口1的模式 */
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收发模式
USART_InitStruct.USART_Parity = USART_Parity_No;// 无奇偶校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1;// 1个停止位
USART_InitStruct.USART_WordLength = USART_WordLength_8b;// 8个数据位
USART_Init(USART1, &USART_InitStruct);
/* 使能串口DMA发送请求 */
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
/* 使能串口1 */
USART_Cmd(USART1, ENABLE);
}
②UART.h文件的代码如下:
c
#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"
#define Buffer_Size 256
extern uint8_t Buff[Buffer_Size];//定义数据缓冲区
void UART1_Init(void);
#endif
③MyDMA.c文件的代码如下:
c
#include "MyDMA.h"
#include "UART.h"
/**
* DMA1的通道4的初始化
*/
void DMA1_Init(void)
{
/* 1、使能DMA1的时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 2、配置DMA1的通道1 */
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); //"外设站点"的起始地址
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址是否自增,这里选择不自增,因为搬运到数据寄存器TDR中
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buff; //"内存站点"起始地址
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度,8位
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //目的站点地址是否自增,自增
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST; //搬运方向的选择(目的地选择),这里选择内存站点--->外设站点:DMA_DIR_PeripheralDST
DMA_InitStruct.DMA_BufferSize = 0; //传输计数器的大小,代表搬运数据的个数,先置为0
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //是否自动重装,这里选择不自动重装
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //是否软件触发,这里选择不是,由硬件触发
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; //优先级,这里选择中等
DMA_Init(DMA1_Channel4,&DMA_InitStruct); //配置DMA1的通道4
// DMA_Cmd(DMA1_Channel4,ENABLE); //使能DMA1的通道4
DMA_Cmd(DMA1_Channel4,DISABLE); //先失能DMA1的通道4
}
/**
* DMA1开启搬运函数
*/
void UART1_DMA1_Transport(uint16_t DataNumber)
{
/* 1、失能DMA1 */
DMA_Cmd(DMA1_Channel4,DISABLE);
/* 2、先设置传输计数器的计数值 */
DMA_SetCurrDataCounter(DMA1_Channel4, DataNumber);
/* 3、使能DMA1 */
DMA_Cmd(DMA1_Channel4,ENABLE);
/* 4、等待搬运完成 */
while(!DMA_GetFlagStatus(DMA1_FLAG_TC4)); //等待DMA1通道4全部搬运完成
DMA_ClearFlag(DMA1_FLAG_TC4); //手动清除标志位
}
④MyDMA.h文件的代码如下:
c
#ifndef __MyDMA_H
#define __MyDMA_H
#include "stm32f10x.h"
void DMA1_Init(void);
void UART1_DMA1_Transport(uint16_t DataNumber);
#endif
⑤主函数main.c文件的代码如下:
c
#include "stm32f10x.h"
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"
#define DataNumber 10 //定义需要发送的数据个数
int main(void)
{
for(uint8_t i = 0; i<DataNumber; i++)//先向缓冲区里面填入数据
{
Buff[i] = i;
}
UART1_Init();
DMA1_Init();
UART1_DMA1_Transport(DataNumber); //开始搬运数据
while(1)
{
}
}
2、DMA中断+串口接收定长数据包
①UART.c文件的代码如下:
c
#include "UART.h"
uint8_t Buff[Buffer_Size]; //定义数据缓冲区
uint16_t Length = 10; //定义定长数据包长度
uint8_t Flag = 0; //传输完成标志位
/**
* 串口1的初始化函数
*/
void UART1_Init(void)
{
/* 开启串口的UART1的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/* 开启串口的GPIO的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 配置串口1的引脚 */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置串口1的模式 */
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
USART_InitStruct.USART_Parity = USART_Parity_No; // 无奇偶校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 1个停止位
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 8个数据位
USART_Init(USART1, &USART_InitStruct);
/* 使能串口DMARx接收请求 */
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
/* 使能串口1 */
USART_Cmd(USART1, ENABLE);
}
/**
* 串口发送多个字节的数据
*/
void USART_SendArray(uint8_t *array, uint16_t len)
{
/* 发送一组数据 */
for (uint16_t i = 0; i < len; i++)
{
USART_SendChar(array[i]);
}
}
②UART.h文件的代码如下:
c
#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"
#define Buffer_Size 256
extern uint8_t Buff[Buffer_Size];//定义数据缓冲区
extern uint16_t Length;
extern uint8_t Flag;
void UART1_Init(void);
void USART_SendArray(uint8_t *array, uint16_t len);
#endif
③MyDMA.c文件的代码如下:
c
#include "MyDMA.h"
#include "UART.h"
/**
* DMA1通道5的初始化
*/
void DMA1_Init(void)
{
/* 1、使能DMA1的时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 2、配置DMA1的通道1 */
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); //外设站点的起始地址
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址是否自增,这里选择不自增,因为搬运到数据寄存器TDR中
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buff; //内存站点起始地址
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度,8位
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //目的站点地址是否自增,自增
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //搬运方向的选择(目的地选择),这里选择外设站点--->内存站点:DMA_DIR_PeripheralSRC
DMA_InitStruct.DMA_BufferSize = Length; //传输计数器的大小,代表搬运数据的个数
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; //是否自动重装,这里选择自动重装
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //是否软件触发,这里选择不是,由硬件触发
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; //优先级,这里选择中等
DMA_Init(DMA1_Channel5,&DMA_InitStruct); //配置DMA1的通道5
/* 3、使能DMA1通道5搬运完成中断和NVIC */
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
DMA_Cmd(DMA1_Channel5,ENABLE); //使能DMA1的通道5
}
/**
* DMA1开启搬运函数
*/
//void UART1_DMA1_Transport(uint16_t DataNumber)
//{
// /* 1、失能DMA1 */
// DMA_Cmd(DMA1_Channel4,DISABLE);
//
// /* 2、先设置传输计数器的计数值 */
// DMA_SetCurrDataCounter(DMA1_Channel4, DataNumber);
//
// /* 3、使能DMA1 */
// DMA_Cmd(DMA1_Channel4,ENABLE);
//
// /* 4、等待搬运完成 */
// while(!DMA_GetFlagStatus(DMA1_FLAG_TC4)); //等待通道4搬运完成
// DMA_ClearFlag(DMA1_FLAG_TC4); //手动清除标志位
//}
/**
* DMA1通道5传输完成的中断服务函数
*/
void DMA1_Channel5_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC5))
{
DMA_ClearFlag(DMA1_FLAG_TC5); //清除通道5的标志位
Flag = 1;
}
}
④主函数main.c文件的代码如下:
c
#include "stm32f10x.h"
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"
int main(void)
{
UART1_Init();
DMA1_Init();
while(1)
{
if(Flag)
{
Flag = 0;
USART_SendArray(Buff, Length);
}
}
}
3、串口空闲中断+DMA接收不定长数据包
①UART.c文件的代码如下:
c
#include "UART.h"
uint8_t Buff[Buffer_Size]; //定义数据缓冲区
uint8_t Flag = 0; //传输完成标志位
uint16_t Index = 0; //定义接收到的数据个数
/**
* 串口1的初始化函数
*/
void UART1_Init(void)
{
/* 开启串口的UART1的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/* 开启串口的GPIO的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 配置串口1的引脚 */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置串口1的模式 */
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
USART_InitStruct.USART_Parity = USART_Parity_No; // 无奇偶校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 1个停止位
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 8个数据位
USART_Init(USART1, &USART_InitStruct);
/* 使能串口DMARx接收请求 */
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
/* 使能串口IDLE空闲中断和NVIC */
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
/* 使能串口1 */
USART_Cmd(USART1, ENABLE);
}
/**
* 串口发送一个字节的数据
*/
void USART_SendChar(uint8_t ch)
{
/* 发送一个字节的数据 */
USART_SendData(USART1, ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
/**
* 串口发送一个字符串的数据
*/
void USART_SendString(uint8_t *str)
{
/* 发送多个字节的数据 */
while (*str!= '\0')
{
USART_SendChar(*str++);
}
}
/**
* 串口发送多个字节的数据
*/
void USART_SendArray(uint8_t *array, uint16_t len)
{
/* 发送一组数据 */
for (uint16_t i = 0; i < len; i++)
{
USART_SendChar(array[i]);
}
}
/**
* 对printf函数进行重定向
*/
int fputc(int ch, FILE *f)
{
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
/* 发送一个字节的数据 */
USART_SendData(USART1, (uint8_t)ch);
return ch;
}
/**
* 串口1的空闲中断服务函数
*/
void USART1_IRQHandler(void)
{
uint8_t Receive_Data;
if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE))
{
Receive_Data = USART1->SR;
Receive_Data = USART1->DR; //清除中断标志位IDLE
// Index = Buffer_Size - DMA_GetCurrDataCounter(DMA1_Channel5);//获取接收到的数据个数
Index = Buffer_Size -(DMA1_Channel5->CNDTR); //获取接收到的数据个数
Flag = 1;
/* 重新给DMA传输计数器设置值:让第二个数据包从缓冲区第一位开始存储 */
DMA_Cmd(DMA1_Channel5,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel5, Buffer_Size);
DMA_Cmd(DMA1_Channel5,ENABLE); //使能DMA1的通道5
}
}
②UART.h文件的代码如下:
c
#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"
#define Buffer_Size 256
extern uint8_t Buff[Buffer_Size];//定义数据缓冲区
extern uint8_t Flag;
extern uint16_t Index;
void UART1_Init(void);
void USART_SendChar(uint8_t ch);
void USART_SendString(uint8_t *str);
void USART_SendArray(uint8_t *array, uint16_t len);
#endif
③MyDMA.c文件的代码如下:
c
#include "MyDMA.h"
#include "UART.h"
/**
* DMA1通道5的初始化
*/
void DMA1_Init(void)
{
/* 1、使能DMA1的时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 2、配置DMA1的通道1 */
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); //外设站点的起始地址
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址是否自增,这里选择不自增,因为搬运到数据寄存器TDR中
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buff; //内存站点起始地址
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度,8位
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //目的站点地址是否自增,自增
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //搬运方向的选择(目的地选择),这里选择外设站点--->内存站点:DMA_DIR_PeripheralDST
DMA_InitStruct.DMA_BufferSize = Buffer_Size; //传输计数器的大小,代表搬运数据的个数
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //是否自动重装,这里选择不自动重装,接收一个数据包,在空闲中断里面重装
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //是否软件触发,这里选择不是,由硬件触发
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; //优先级,这里选择中等
DMA_Init(DMA1_Channel5,&DMA_InitStruct); //配置DMA1的通道5
DMA_Cmd(DMA1_Channel5,ENABLE); //使能DMA1的通道5
}
④主函数main.c文件的代码如下:
c
#include "stm32f10x.h"
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"
int main(void)
{
UART1_Init();
DMA1_Init();
while(1)
{
if(Flag)
{
Flag = 0;
USART_SendArray(Buff, Index);
}
}
}