打印
[应用相关]

串口DMA的应用

[复制链接]
658|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
晓伍|  楼主 | 2019-7-5 08:32 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
记录一下串口dma的使用,dma的好处在于他的传输是不需要经过CPU的,可以实现内存和外设的直接双向通信。合理使用dma能使程序设计变得简单。以串口3为实例介绍一下串口dma的配置过程,其他串口也是一样的,只需要修改一下dma的通道。

  首先串口的配置过程与常规的配置基本一致,不过说明一点就是,如果使用dma接收不定长数据的话,比较常用的一种方法是利用空闲中断。所以在配置的时候就不要使能RXNE的中断了。


使用特权

评论回复
沙发
晓伍|  楼主 | 2019-7-5 08:33 | 只看该作者
关于串口初始化的时候中断的配置

  

//关闭RXNE 开启IDLE
        USART_ITConfig(USART3, USART_IT_TC,DISABLE);
        USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);
        USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);

使用特权

评论回复
板凳
晓伍|  楼主 | 2019-7-5 08:33 | 只看该作者
串口初始化完成后进行的是dma的配置

//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
        NVIC_InitTypeDef  NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;  
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;  
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
        NVIC_Init(&NVIC_InitStructure);  
       
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        //使能DMA传输
       
        DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值

        DMA1_MEM_LEN=cndtr;
        DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设基地址
        DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //数据传输方向,从内存读取发送到外设
        DMA_InitStructure.DMA_BufferSize = cndtr;  //DMA通道的DMA缓存的大小
        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_VeryHigh; //DMA通道 x拥有中优先级
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
        DMA_Init(DMA_CHx, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器

        DMA_Cmd(DMA_CHx,ENABLE);  
}

使用特权

评论回复
地板
晓伍|  楼主 | 2019-7-5 08:33 | 只看该作者
这是接收部分的设置。需要注意的一个成员变量是DMA_DIR,传输方向的设置。从外部数据到串口外设使用DMA_DIR_PeripheralSRC来初始化dma,而从串口传输到外部则使用DMA_DIR_PeripheralDST来初始化dma。因此初始化发送和接收两个通道的时候,可以使用两个函数,仅有一个参数不同。当然也可以在编写底层驱动的时候就把这个成语列入入口参数,上层程序在调用时传入方向的配置即可。

使用特权

评论回复
5
晓伍|  楼主 | 2019-7-5 08:34 | 只看该作者
接下来是中断服务函数,我们配置的是空闲中断,即外设从发起一起请求后到接收完这一批数据之后产生的一个中断,因此我们不需要像RNXE中断那样每收一个保存一个,而是当有空闲中断产生的时候数据已经接受完了。数据就保存在配置DMA和外设的映射的时候的数组里。所以我们的中断任务就是把这批数据搬移到缓冲区里,供应用程序使用。同时标记串口接收完成标志(由自己定义,可以是单独的一个变量,也可以是变量的某一个位)。

void RX3_IT_Handler(void)
{
        uint16_t temp = 0;  
    uint16_t i = 0;  
#ifdef OS_TICKS_PER_SEC                 //如果时钟节拍数定义了,说明要使用ucosII了.
OSIntEnter( );
#endif
    //每一个空闲中断为一个数据
    if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)  
    {  
        temp = USART_ReceiveData( USART3 );//清标志位
        DMA_Cmd(DMA1_Channel3,DISABLE); //禁止DMA 接收
                //获取数据
                temp = RX3_Recv_Len - DMA_GetCurrDataCounter(DMA1_Channel3);  
                for (i = 0;i < temp;i++)  
        {  
            RX3_Buff[i] = RX3_Temp[i];  
        }
                RX3_Point=temp;
                RX3_Point|=0x80;
                //重装DMA数值
                DMA_SetCurrDataCounter(DMA1_Channel3,RX3_Recv_Len);  
                //开启DMA
        DMA_Cmd(DMA1_Channel3,ENABLE);   
    }
        __nop();
               
#ifdef OS_TICKS_PER_SEC                 //如果时钟节拍数定义了,说明要使用ucosII了.
                OSIntExit( );                                                                                           
#endif  
}

使用特权

评论回复
6
晓伍|  楼主 | 2019-7-5 08:34 | 只看该作者
开启一次dma传输 (主要针对串口发送数据,在正确配置好串口dma发送通道后再调用)
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
        DMA_Cmd(DMA_CHx, DISABLE );  //关闭USART1 TX DMA1 所指示的通道      
        DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
        DMA_Cmd(DMA_CHx, ENABLE);  //使能USART1 TX DMA1 所指示的通道
}

使用特权

评论回复
7
晓伍|  楼主 | 2019-7-5 08:34 | 只看该作者
开启串口dma接收,再串口初始化完成后调用,这样dma接收就完成了。
void u3_dma_init(void)
{
        //关闭RXNE 开启IDLE
        USART_ITConfig(USART3, USART_IT_TC,DISABLE);
        USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);
        USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);

        MYDMA_Config(DMA1_Channel3,(u32)&USART3->DR,(u32)RX3_Temp,RX3_Recv_Len);//RX
        //使能DMA收发
        USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE);
}

使用特权

评论回复
8
晓伍|  楼主 | 2019-7-5 08:35 | 只看该作者
应用接口函数:串口dma发送,若发送的是不定长数据,可以在数据的结尾处添加结束标志,这样dma就可以只发送对应长度的数据。
void u3_dma_sendData(u8 *buff)
{
                u8 i=0;
                unsigned char* p_str;
                p_str=buff;
                //检测字符串结尾
                //确保串口不会乱发数据
                while(*p_str != TXEND_SYMBOL || *( p_str + 1 ) != TXEND_SYMBOL){
                        p_str++;
                        if(i++>70){
                                MyPrintf("error, not found txend_symbol\r\\n");
                                return ;
                        }
                }
               
                usart_dma_init(DMA1_Channel2,(u32)&USART3->DR,(u32)buff,i);//TX
                USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE);
                MYDMA_Enable(DMA1_Channel2);
               
                while(1){
                        if(DMA_GetFlagStatus(DMA1_FLAG_TC2)!=RESET)        //判断通道4传输完成
                        {
                                DMA_ClearFlag(DMA1_FLAG_TC2);//清除通道4传输完成标志
                                break;
                        }
                        DMA_GetCurrDataCounter(DMA1_Channel2);//得到当前还剩余多少个数据
                }
//                MyPrintf("send done!\r\n");
}

使用特权

评论回复
9
晓伍|  楼主 | 2019-7-5 08:35 | 只看该作者
其中usart_dma_init函数和MYDMA_Config类似,仅有方向这一成语变量不同。这里不再列出。
关于串口的接收,在上层可以写一个扫描的函数,不断检查接收完成的标志位。

至此,串口dma的收发都已经编写完成。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

3923

帖子

1

粉丝