环境
- 硬件平台:STM32F103ZET6
- 开发环境:KEIL5
DMA
DMA(Direct Memory Access ,直接存储器存取),是一种可以大大减轻CPU工作量的数据存取方式,因而被广泛使用,STM32中DMA是以类似外设的形式添加到内核之外。当有大量的数据需要在内存与外设之间搬运时,使用DMA模式可以提高传输效率和整体运行性能。
F103具有2个DMA,DMA1有7个通道,DMA2有5和通道,每个通道对应不同的外设,在这次开发中,使用的USART1的接收和发送分别用到了DMA1的通道4和5。
串口初始化
/**
* @ Function Name : usart_init
* @ Author : hlb
* @ Brief : 串口初始化
* @ Date : 2017.07.18
* @ Modify : ...
**/
void usart_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//串口数据初始化
usart_data_init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10
//USART 初始化设置
USART_InitStructure.USART_BaudRate = BOUNDRATE; //串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
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); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
usart1_dma_init(); // 初始化dma
}
DMA初始化
在同一般的串口初始化配置后,进行串口DMA的初始化。
由于使用双缓冲发送,DMA发送通道的源地址暂时设置为空,在之后的缓冲区激活和锁定当中,变换源地址。
DMA接收通道源地址为接收缓冲区的地址。
/**
* @ Function Name : usart1_dma_init
* @ Author : hlb
* @ Brief : 初始化串口1的dma。
* @ Date : 2017.07.18
* @ Modify : ...
**/
void usart1_dma_init(void)
{
DMA_InitTypeDef dma_initstruct;
NVIC_InitTypeDef NVIC_InitStructure;
/*------------------------------TX_DMA----------------------------------*/
//DMA发送中断设置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 重置TX_DMA通道
DMA_DeInit(USART1_TX_DMA_CHANNEL);
// 使能时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
dma_initstruct.DMA_DIR = DMA_DIR_PeripheralDST; // 设置dma的方向由内存到外设
dma_initstruct.DMA_M2M = DMA_M2M_Disable; // 禁止内存到内存的传输
dma_initstruct.DMA_BufferSize = USART_TX_LEN; // 设置DMA在传输时缓冲区的长度
dma_initstruct.DMA_MemoryBaseAddr = null; // 设置源地址
dma_initstruct.DMA_PeripheralBaseAddr = (u32)&(USART1->DR); // 设置传输地址
dma_initstruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 每次传输单位为字节
dma_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 允许内存自增地址
dma_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 禁止外设自增
dma_initstruct.DMA_Mode = DMA_Mode_Normal; // 普通模式
dma_initstruct.DMA_Priority = USART1_TX_DMA_PRIORITY; // 设置DMA通道的优先级
// 初始化DMA1的4通道(USART1, TX)
DMA_Init(USART1_TX_DMA_CHANNEL, &dma_initstruct);
/*------------------------------RX_DMA----------------------------------*/
DMA_Cmd(USART1_RX_DMA_CHANNEL, DISABLE); // 关DMA通道
DMA_DeInit(USART1_RX_DMA_CHANNEL);
dma_initstruct.DMA_MemoryBaseAddr = (u32)UsartRxBuffer.Buff; // 设置源地址
dma_initstruct.DMA_PeripheralBaseAddr = (u32)&(USART1->DR); // 设置传输地址
dma_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC; // 由外设到内存
dma_initstruct.DMA_BufferSize = USART_RX_LEN; // 设置DMA在传输时缓冲区的长度
dma_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 禁止外设自增
dma_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 允许内存自增地址
dma_initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据字长
dma_initstruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据字长
dma_initstruct.DMA_Mode = DMA_Mode_Circular; // 循环接收
dma_initstruct.DMA_Priority = USART1_RX_DMA_PRIORITY; // 设置DMA通道的优先级
dma_initstruct.DMA_M2M = DMA_M2M_Disable; // 禁止内存到内存的传输
// 初始化DMA1的5通道(USART1, RX)
DMA_Init(USART1_RX_DMA_CHANNEL, &dma_initstruct);
//使能接收通道
DMA_Cmd(USART1_RX_DMA_CHANNEL,ENABLE);
// 允许串口DMA
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
}
DMA传输设置
/**
* @ Function Name : dma_usart_tx_restart
* @ Author : hlb
* @ Brief : 重启串口的dma
* @ Date : 2017.07.18
* @ Input : u8 *res 发送的数据
u32 size 传输数据的长度
* @ Modify : ...
**/
void dma_usart_tx_restart(u8 *res, u32 size)
{
// 禁用DMA
DMA_Cmd(USART1_TX_DMA_CHANNEL, DISABLE);
// 设置DMA的传输值
USART1_TX_DMA_CHANNEL -> CNDTR = size;
// 设置传输地址
USART1_TX_DMA_CHANNEL -> CMAR = (u32)res;
// 启动DMA
DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE)
}
/**
* @ Function Name : dma_usart_tx
* @ Author : hlb
* @ Brief : 串口DMA发送函数
* @ Date : 2017.07.19
* @ Input : u8* buff 待发送数组头指针
* u32 size 待发送数组大小
* @ Modify : ...
**/
void dma_usart_tx(u8 *buff, u32 size)
{
// 清除TC标志
USART_ClearFlag(USART1, USART_FLAG_TC);
//重启DMA发送
dma_usart_tx_restart(buff, size);
}
DMA状态查询
/**
* @ Function Name : usart1_get_tx_dma_tc_state
* @ Author : hlb
* @ Brief : 取串口1的发送dma传输标志位
* @ Date : 2017.07.18
* @ OutPut : 串口1发送DMA传送完成标志位
* @ Modify : ...
**/
bool usart1_get_tx_dma_tc_state(void)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET)
{
DMA_ClearFlag(DMA1_FLAG_TC4);
return true;
}
else
{
return false;
}
}
/**
* @ Function Name : dma_usart_chek_tx_state
* @ Author : hlb
* @ Brief : DMA发送状态查询
* @ Date : 2017.07.19
* @ Modify : ...
**/
void dma_usart_chek_tx_state(void)
{
//如果DMA为忙
if(UsartTxBuffer.DmaBusy)
{
// 查询dma的完成标志
if (usart1_get_tx_dma_tc_state())
{
UsartTxBuffer.DmaBusy = false;
}
}
}
双缓冲
当数据处理中的生产者速度大于消费者的时候,使用一般的循环缓冲队列会造成数据的丢失和混乱,这时候可以考虑使用双缓冲。
所谓双缓冲,就是使用两个缓冲区装载数据,一个用于生产者,另一个用于消费者,当消费者将其所在的缓冲区使用完毕,生产者在其缓冲区又有新的数据产生后,二者交换两个缓冲区的使用权。从而可以达到数据的发送与生产互不干涉,存取流畅。
在串口DMA发送中,DMA作为消费者,发送缓冲区数据,CPU作为数据生产者,向提供的发送接口填充数据。当DMA非忙的时候,消费者已经将缓冲耗尽,交换缓冲区。
缓冲区结构体定义
#pragma pack(push, 1)
//串口数据缓冲定义
typedef struct
{
u8 Buff[TX_BUFFER_NUM_DEFAULT][USART_TX_LEN]; //缓冲区
u16 Idx[TX_BUFFER_NUM_DEFAULT]; //添加索引
u8 PartAvailAction; //激活缓冲区位置
bool DmaBusy; //发送DMA是否为忙
}Usart_Tx_Buff_TypeDef;
#pragma pack(pop)
DMA发送处理函数
/**
* @ Function Name : dma_usart_tx_handle
* @ Author : hlb
* @ Brief : dma串口发送处理函数
* @ Date : 2017.07.19
* @ Modify : ...
**/
void dma_usart_tx_handle(void)
{
//如果DMA非忙
if(!UsartTxBuffer.DmaBusy)
{
//激活的缓冲区非空
if(UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction] != BUFFER_HEAD_DEFAULT)
{
//设置DMA传输对象
dma_usart_tx(UsartTxBuffer.Buff[UsartTxBuffer.PartAvailAction], \
UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction]);
//恢复缓冲区索引
UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction] = BUFFER_HEAD_DEFAULT;
//激活另外一个缓冲,锁定当前缓冲
UsartTxBuffer.PartAvailAction = 1 - UsartTxBuffer.PartAvailAction;
//锁定DMA
UsartTxBuffer.DmaBusy = true;
}
}
}
串口数据发送接口
/**
* @ Function Name : usart_tx
* @ Author : hlb
* @ Brief : 串口发送接口
* @ Date : 2017.07.19
* @ Input : u8 *buff 发送的缓冲地址
* u32 size 发送的数据长度
* @ Modify : ...
**/
void usart_tx(u8 *buff, u32 size)
{
u32 freeSize = 0;
freeSize = USART_TX_LEN - UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction];
if(freeSize >= size)
{
memcpy(&UsartTxBuffer.Buff[UsartTxBuffer.PartAvailAction][UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction]], \
buff, \
size);
}
UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction] += size;
}
串口数据发送线程
/**
* @ Function Name : usart_send_handle
* @ Author : hlb
* @ Brief : 串口数据发送线程
* @ Date : 2017.07.19
* @ Modify : ...
**/
void usart_send_handle(void)
{
//检查DMA发送状态
dma_usart_chek_tx_state();
//发送处理线程
dma_usart_tx_handle();
}
环形缓冲
环形缓冲区是一个先进先出的缓冲区,是通信中常用的缓冲模式。
通常环形缓冲拥有一个读指针和一个写指针,生产者控制写指针,消费者控制读指针。
缓冲区结构体定义
#pragma pack(push, 1)
//串口数据缓冲定义
typedef struct
{
u8 Buff[USART_RX_LEN]; //缓冲区
u16 AddIdx; //添加索引
u16 GetIdx; //取出索引
}Usart_Rx_Buff_TypeDef;
#pragma pack(pop)