RTThread使用DMA串口接收数据不连续的问题

Posted Rabbit-susu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTThread使用DMA串口接收数据不连续的问题相关的知识,希望对你有一定的参考价值。

RTThread使用DMA接收串口数据的问题

问题/现象

使用RTThread的DMA接收串口数据,数据不连续,即IDLE中断没有起到作为一个frame的判定.

经过对serial和drv_uarts源码的分析,得出原因:

graph LR RX_INT[USART1_IRQHandler] -->process1(...) process1 -->rx_isr1[dma_recv_isr] rx_isr1 -->flag1isr_flag flag1 -->|UART_RX_DMA_IT_IDLE_FLAG| serial_isr1[rt_hw_serial_isr] serial_isr1 -->eventevent DMA_INT[UART1_DMA_RX_IRQHandler] -->process2(...) process2 -->rx_isr2[dma_recv_isr] rx_isr2 -->flag2isr_flag flag2 -->|UART_RX_DMA_IT_HT_FLAG| serial_isr2[rt_hw_serial_isr] flag2 -->|UART_RX_DMA_IT_TC_FLAG| serial_isr2[rt_hw_serial_isr] serial_isr2 -->eventevent event -->|RT_SERIAL_EVENT_RX_DMADONE|do_something(...)

从上图可知,发生IDLE中断时,USART1_IRQHandler调用的是和UART1_DMA_RX_IRQHandler相同的接口 —— rt_hw_serial_isr.

这就造成无法区分是IDLE中断还是DMA中断.


不改变源码的情况下,仍使用DMA+IDLE中断,目前这两种方式是比较好的

解决方式①

  • 接收数据
/* 接收数据回调函数 */
static rt_err_t uart2_input(rt_device_t dev, rt_size_t size)

    rt_err_t result;
    result = rt_sem_release(&serial2_sem);/*通知serial_thread_entry线程,有数据了*/
    if ( result == RT_EOK)
    
        rt_kprintf("sem release error!\\n");
    
    return result;


static void serial_thread_entry(void *parameter)

    rt_err_t result;
    rt_uint8_t c = 0;
    rt_uint8_t i = 0, rx_state = SERIAL2_STATE_WAIT_FRAME;
    rt_device_t serial2 = rt_device_find("uart2");
    struct frame_msg msg;
    while (1)
    
		switch(rx_state)
		
			case SERIAL2_STATE_WAIT_FRAME:
			
				result = rt_sem_take(&serial2_sem, RT_WAITING_FOREVER); /* 等待新的一帧数据 */
				if (result == RT_EOK)
				
					rt_kprintf("%s: frame start\\n", __func__);
					rx_state = SERIAL2_STATE_RECV_DATA;
					i = 0;
				
				break;
			
			case SERIAL2_STATE_RECV_DATA:
			
				/* 读取一段数据 —— 数个字节(不足一帧) */
				while(rt_device_read(serial2, 0, &c, 1) != 0)
				
					framebuf[i] = c;
					i++;
				

				result = rt_sem_take(&serial2_sem, 10); /* 将信号量设置为带有超时的信号量,等待下一段数据 */
				if(result == -RT_ETIMEOUT)  /* 超过10个OSTicks没有读到数据,判定该帧结束 */
				
					framebuf[i] = \'\\0\';
					msg.data = framebuf;
					msg.size = i;
					result = rt_mq_send(&rtc_rx_mq, (void*)&msg, sizeof(struct frame_msg)); /* 给其它线程通信 */
					if(result != RT_EOK)
					
						rt_kprintf("%s: msgqueue send error-[%d]\\n", __func__, result);
					
					rx_state = SERIAL2_STATE_WAIT_FRAME;    /* 一帧结束 */
					rt_kprintf("%s: frame end\\n", __func__);
				
				else
				
					/*接收下一段数据*/
					rt_kprintf("%s: frame recv ing...\\n", __func__);
				
				break;
			
			default:
				break;
		
    

解决方式②

把信号量换成消息队列.


其它问题

代码中的几个局部变量在线程中具有了和线程一样长的生成周期

life_circle(serial_thread_entry) = life_circle(variable(i))
                                = life_circle(variable(rx_state))
                                = life_circle(variable(serial2))
                                = ...

这样是不是意味着thread的stack一直被占用?

嗯,是的.

DMA接收串口数据(定长与不定长)

效果如图

串口配置(可以直接拷贝拿去用)

#include "sys.h"
#include "usart.h"	  
// 	 
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
 
	int handle; 

; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
 
	x = x; 
 
//重定义fputc函数 
int fputc(int ch, FILE *f)
      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;

#endif 
#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart_init(u32 bound)
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	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  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	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_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串口1的DMA发送
  



void USART1_IRQHandler(void)                	//串口1中断服务程序
	
	u8 Res;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		  		 
      
 
#endif	


DMA配置

#include "dma.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK精英STM32开发板
//DMA 代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/8
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved									  
//

DMA_InitTypeDef DMA_InitStructure;

u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度 	    
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量 

void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)

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_Medium; //DMA通道 x拥有中优先级 
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Rx_DMA 所标识的寄存器
USART_Cmd(USART1, ENABLE); //使能串口1 
DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道 
MYDMA_Enable(DMA1_Channel5);//开始一次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 所指示的通道 
	  


主函数(定长)

#include "stm32f10x.h"
#include "usart.h"	
#include "delay.h"
#include "dma.h"
#include "string.h"
 int main(void)
 	
  u8 USART_RX_BUF1[50];
  delay_init();
  uart_init(115200);
  MYDMA_Config(DMA1_Channel5,(u32)&USART1->DR,(u32)USART_RX_BUF1,50);//DMA1通道5,外设为串口1,存储器为SendBuff,长度35,
  while(1)

   
if(DMA_GetFlagStatus(DMA1_FLAG_TC5)!=RESET)	//判断通道5传输完成

DMA_ClearFlag(DMA1_FLAG_TC5);//清除通道4传输完成标志
delay_ms(10); //延时10ms,让DMA继续接收后面数据的同时,也能跑跑其它进程 
printf("1:%s\\r\\n",USART_RX_BUF1); //打印
memset(USART_RX_BUF1,0,35);    //清空数组
MYDMA_Enable(DMA1_Channel5);//开始一次DMA传输!


 

主函数(不定长)

#include "stm32f10x.h"
#include "usart.h"	
#include "delay.h"
#include "dma.h"
#include "string.h"
 int main(void)
 	
  u8 USART_RX_BUF1[50];
  delay_init();
  uart_init(115200);
  MYDMA_Config(DMA1_Channel5,(u32)&USART1->DR,(u32)USART_RX_BUF1,50);//DMA1通道5,外设为串口1,存储器为SendBuff,长度35,
  while(1)

   
if(USART_RX_BUF1[0]!=0)	//判断通道4传输完成

delay_ms(10); //延时10ms,让DMA继续接收后面数据的同时,也能跑跑其它进程 
printf("1:%s\\r\\n",USART_RX_BUF1); //打印
memset(USART_RX_BUF1,0,35);    //清空数组
MYDMA_Enable(DMA1_Channel5);//开始一次DMA传输!


 

标志位说明

以上是关于RTThread使用DMA串口接收数据不连续的问题的主要内容,如果未能解决你的问题,请参考以下文章

STM32F4 DMA接收串口定长数据,串口每秒来1000个数据,使用DMA-Normal模式

STM32使用DMA接收串口数据

我在用STM32串口DMA接收数据时,为啥在接收过程中,我的程序停止运行了,接收完成后又开始运行,求解?

RT-Thread串口接收的BUG(DMA缓存区太小)

stm32 高效串口收发

65 STM32F0系列 串口DMA循环接收实验记录