一种串口DMA收发不定长数据的方法

2019-12-09 19:17发布

以前发过利用DMA接收不定长数据的方法,最近把发送也调试好了,这个程序应用多年,一直很好用,适应各种设备,现把程序再整合一下发上来。

主要原理:
1、串口接收:在内存中开启一个缓冲区,DMA指针指向缓冲区首地址,开启接收,并定时判断是否有数据接收到,如果接收到,则继续等待,如果无数据超过规定时间,则认为本帧数据结束,将数据拷贝至其他部位,同时进行下次接收;
2、串口发送:DMA工作与Normal模式,需要发送时,设置DMA指针到发送数组首地址,DMA长度设置为要发送的数据长度,启动DMA后即可开始发送。

优点:
1、无需任何中断,只需要定时调用串口接收函数;
2、收发占用CPU时间极少,经测试,接收和发送函数平时占用时间大约3-10us(48M主频)
3、串口超时时间随意设定,适应多种应用

下面是程序(本程序是在STM32F373上调试的,其他CPU只要改下寄存器名即可使用):

首先是USART.h
#ifndef __USART_H
#define __USART_H

#define        UART2_TimeoutComp 5  //串口超时时间2*5=10ms

#define        USART_BufferSize2 100  //接收缓冲区大小

#define SRC_USART2_RDR (&(USART2->RDR)) //串口接收寄存器
#define SRC_USART2_TDR (&(USART2->TDR)) //串口发送寄存器

extern u8 USART2_Data[USART_BufferSize2];

void USART_Configuration(void);
void USART_Write(u8* dat,u16 len);
u16 USART_Read(void);

#endif

接下来是USART.c

#include "stm32_includes.h"
#include "USART.h"

//串口接收DMA
DMA_InitTypeDef DMA_InitStructure_Rx;

//串口发送DMA
DMA_InitTypeDef DMA_InitStructure_Tx;

//串口2接收到的数据
u8 USART2_Data[USART_BufferSize2];

//串口2超时
u8 UART2_Timeout;

//串口2接收缓存
u8 uart2_data_temp[USART_BufferSize2];

//串口2上次接收指针位置
u16 uart2_Counter_Last=0;

/////////////////////////////////////////////////////////发送接收程序/////////////////////////////////////////////////////////////////////
//发送多个字节数据
void USART_Write(u8* dat,u16 len)
{
        DMA_Cmd(DMA1_Channel7, DISABLE); //关闭DMA        

        DMA_InitStructure_Tx.DMA_MemoryBaseAddr = (u32)dat; //目标BUF       
        DMA_InitStructure_Tx.DMA_BufferSize = len; //DMA缓存的大小
       
        DMA_Init(DMA1_Channel7, &DMA_InitStructure_Tx); //根据DMA_InitStruct中指定的参数初始化DMA的通道7寄存器
       
        DMA_Cmd(DMA1_Channel7, ENABLE); //开启DMA                
}
//重置串口接收DMA
void USART_ResetDMA_Rx(void)
{

        DMA_Cmd(DMA1_Channel6, DISABLE); //关闭DMA
        DMA_ClearFlag(DMA1_FLAG_TC6);  //清除标志
        DMA1_Channel6->CNDTR = USART_BufferSize2; //重置DMA指针
        DMA_Cmd(DMA1_Channel6, ENABLE); //开启DMA
        UART2_Timeout=0; //清零超时时间
        uart2_Counter_Last = USART_BufferSize2; //重置上次接收指针

}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//DMA初始化       
void USART_DMA_Init(void)
{
        //串口接收DMA初始化
        DMA_DeInit(DMA1_Channel6); //将DMA的通道6寄存器重设为缺省值
       
        DMA_InitStructure_Rx.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作源头//外设是作为数据传输的目的地还是来源
        DMA_InitStructure_Rx.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不递增
        DMA_InitStructure_Rx.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
        DMA_InitStructure_Rx.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节为单位
        DMA_InitStructure_Rx.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //内存字节为单位
        DMA_InitStructure_Rx.DMA_Mode = DMA_Mode_Circular; //工作在循环模式
        DMA_InitStructure_Rx.DMA_Priority = DMA_Priority_High;
        DMA_InitStructure_Rx.DMA_M2M = DMA_M2M_Disable;

        DMA_InitStructure_Rx.DMA_PeripheralBaseAddr = (u32)SRC_USART2_RDR; //源头寄存器
        DMA_InitStructure_Rx.DMA_MemoryBaseAddr = (u32)uart2_data_temp; //目标BUF        
        DMA_InitStructure_Rx.DMA_BufferSize = USART_BufferSize2; //DMA缓存的大小
        DMA_Init(DMA1_Channel6, &DMA_InitStructure_Rx); //初始化DMA的通道7寄存器
       
        DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, DISABLE); //关闭DMA6传输完成中断
       
        USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE); //使能USART2的接收DMA请求

        DMA_Cmd(DMA1_Channel6, ENABLE); //正式允许DMA        
       
       
        //串口发送DMA初始化
        DMA_DeInit(DMA1_Channel7); //将DMA的通道7寄存器重设为缺省值
       
        DMA_InitStructure_Tx.DMA_DIR = DMA_DIR_PeripheralDST; //外设作目的地
        DMA_InitStructure_Tx.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不递增
        DMA_InitStructure_Tx.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
        DMA_InitStructure_Tx.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节为单位
        DMA_InitStructure_Tx.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //内存字节为单位
        DMA_InitStructure_Tx.DMA_Mode = DMA_Mode_Normal; //工作在单次模式       
        DMA_InitStructure_Tx.DMA_Priority = DMA_Priority_High;
        DMA_InitStructure_Tx.DMA_M2M = DMA_M2M_Disable;         

        DMA_InitStructure_Tx.DMA_PeripheralBaseAddr = (u32)SRC_USART2_TDR; //源头BUF
        DMA_InitStructure_Tx.DMA_MemoryBaseAddr = (u32)uart2_data_temp; //目标BUF
        DMA_InitStructure_Tx.DMA_BufferSize = USART_BufferSize2; //DMA缓存的大小
       
        DMA_Init(DMA1_Channel7, &DMA_InitStructure_Tx); //初始化DMA的通道7寄存器
       
        DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, DISABLE); //关闭DMA7传输完成中断
       
        USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE); //使能USART2的发送DMA请求       
}
//串口初始化       
void USART_Configuration(void)
{
    //串口初始化数据结构定义
        USART_InitTypeDef USART_InitStructure;
       
        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_InitStructure.USART_BaudRate            = 115200;
        USART_Init(USART2, &USART_InitStructure);       
        USART_Cmd(USART2, ENABLE);
       
        USART_DMA_Init();
}

//串口接收读取程序
u16 USART_Read(void)
{
        u16 counter,ret = 0 ;

        //超时时间
        UART2_Timeout++;
       
        //获取接收指针位置
        counter = DMA_GetCurrDataCounter(DMA1_Channel6);
       
        //指针发生变化,说明有数据接收到
        if(counter != uart2_Counter_Last)  //未完成传输
        {
                UART2_Timeout = 0;  //清零超时时间
                uart2_Counter_Last = counter; //保存本次指针位置
        }
        else //指针没有变化,说明没有数据接收到
        {
                if(UART2_Timeout >= UART2_TimeoutComp)  //产生超时
                {
                        if(uart2_Counter_Last < USART_BufferSize2) //有数据接收到
                        {
                                //取得接收到的数据数量
                                ret = USART_BufferSize2 - uart2_Counter_Last;

                                //将接收到的数据复制到接收数组中
                                CopyData(uart2_data_temp,USART2_Data,ret);

                                //重置串口接收DMA,继续下次接收
                                USART_ResetDMA_Rx();                               
                        }
                       
                        //清零超时时间
                        UART2_Timeout=0;
                }
        }

        return ret;
}

接下来是主程序

//--------------------------------------------------------------------------------------------------------------
//数据接收程序
void USART_Task(void)
{               
        len = USART_Read();       
       
        if(len > 0)
        {
                               
                //分析数据
                AnalizeData(USART2_Data, len);       

        }       

        return;
}

int main()
{
        while(1)
        {
                USART_Task();  //调用接收程序
                delay_ms(2);  //2ms调用一次
        }
}


发送数据时,只需要调用发送函数USART_Write即可,数据长度随意。





友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
45条回答
zcllom
2019-12-11 22:52
这种串口处理方式在某些应用场景下很合适,比如主问从答,双方都可以等,各自忙其他的事,然后抽空看下串口有无数据,数据有无变化等等通讯进展情况,甚至数据已经收发完成,由于系统不是中断机制,而没有及时响应处理。而在某些实时性要求高的场景下有局限性,比如面板操控变频器。

一周热门 更多>