在进行stm32开发时,有时会遇到这种情况:需要在设备间进行数据传输,由于stm32串口RDR和TDR寄存器都是8位有效的,我们往往需要定义传输协议(如一帧数据中,包含包含帧头、帧ID、数据帧、校验帧等若干8位数据)。我们希望可以一次收到一帧数据,并进行解码操作。利DMA+串口空闲中断可以有效完成上述任务。
DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:
通常如下图配置:
方向 | 指针情况 |
---|---|
外设 → 存储器 | 外设指针不变,存储buf指针递增 |
存储器 → 外设 | 存储buf指针不变, 外设指针递增 |
存储器 → 存储器 | 都递增 |
关于DMA还有双缓冲区模式、突发传输等等其他设置,一般用不到,具体查询《stm32中文参考手册》
文章开头情景的示例代码如下(DMA配置部分):
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量
void DMA_Config(DMA_Stream_TypeDef *DMA_Streamx,uint32_t chx,uint32_t par,uint32_t mar,uint32_t dir,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar; //DMA 存储器0地址
DMA_InitStructure.DMA_DIR = dir; //direction of transmit.
DMA_InitStructure.DMA_BufferSize = ndtr; //数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure);
DMA_Cmd(DMA_Streamx,ENABLE);
}
//开启一次DMA传输
void DMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
DMA_Cmd(DMA_Streamx, DISABLE); //先关闭DMA,才能设置它
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //等待传输结束
DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //设置传输数据长度
DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA
}
示例代码如下
void My_USART1_Init(void)
{
GPIO_InitTypeDef GPIO_Initstructure;
USART_InitTypeDef USART_Initstructure;
NVIC_InitTypeDef NVIC_Initstrcuture;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE );
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE );
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;//IO³õʼ»¯RX
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
USART_Initstructure.USART_BaudRate = 9600;
USART_Initstructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Initstructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Initstructure.USART_Parity = USART_Parity_No;
USART_Initstructure.USART_StopBits = USART_StopBits_1;
USART_Initstructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_Initstructure);
USART_Cmd(USART1,ENABLE);
USART_ITConfig (USART1,USART_IT_RXNE ,ENABLE);//开启串口接受中断
NVIC_Initstrcuture.NVIC_IRQChannel = USART1_IRQn;
NVIC_Initstrcuture.NVIC_IRQChannelCmd = ENABLE ;
NVIC_Initstrcuture.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Initstrcuture.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_Initstrcuture);
}
void USART1_IRQHandler(void)
{
u8 res=0;
if(USART_GetITStatus(USART1,USART_IT_RXNE))//串口非空标志位为1,收到数据
{
res = USART_ReceiveData(USART1);//读取最新一个收到的数据
USART_SendData(USART1,res );//发送数据
}
}
利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:
示例代码如下:
void USART1_Init(uint32_t bound)//DMA2_Stream2
{
GPIO_InitTypeDef GPIO_Initstructure;
USART_InitTypeDef USART_Initstructure;
NVIC_InitTypeDef NVIC_Initstrcuture;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE );
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE );
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;
GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
USART_Initstructure.USART_BaudRate = bound;
USART_Initstructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Initstructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Initstructure.USART_Parity = USART_Parity_No;
USART_Initstructure.USART_StopBits = USART_StopBits_1;
USART_Initstructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_Initstructure);
NVIC_Initstrcuture.NVIC_IRQChannel = USART1_IRQn;
NVIC_Initstrcuture.NVIC_IRQChannelPreemptionPriority=1;
NVIC_Initstrcuture.NVIC_IRQChannelSubPriority =2;
NVIC_Initstrcuture.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Initstrcuture);
// USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);//开启DMA接收
USART_Cmd(USART1, ENABLE);
//initialize the DMA channel.
DMA_Config(DMA2_Stream2,DMA_Channel_4,
(uint32_t)&(USART1->DR), //串口DR寄存器
(uint32_t)USART1_Rx_Buffer,//自定义的接收数据buf
DMA_DIR_PeripheralToMemory,//外设到存储器方向
USART1_RX_BUFFER_SIZE/2);//长度
}
void USART1_IRQHandler(void)
{
uint8_t rc_tmp;
uint16_t rc_len;
uint16_t i;
if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET)
{
rc_tmp=USART1->SR;
rc_tmp=USART1->DR;//软件序列清除IDLE标志位
DMA_Cmd(DMA2_Stream2, DISABLE);关闭DMA,准备重新配置
DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2); // Clear Transfer Complete flag
DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TEIF2); // Clear Transfer error flag
rc_len = USART1_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA2_Stream2);//计算接收数据长度
for(i=0;i
上述代码经stm32f407平台测试通过
感谢qq_20246035在评论区提出问题。之前一、9部分的实例代码中,DMA_Config函数定义时,数据传输方向直接写死到配置中了,而二、2部分的示例代码中,调用DMA_Config时把数据传输方向作为参数了,运行应该会报错示例和声明不匹配。
两种改正方法:1.调用时不要写传输方向参数 ; 2.修改DMA_Config。目前已按方法二改正
再次感谢读者帮我找到错误,谢谢!
如果大家发现还有什么问题,欢迎在评论区告诉我