您的位置:首页 > 其它

  【原创】STM32 UART DMA实现未知数据长度接收

2012-06-18 23:26 453 查看
串口通信是经常使用到的功能,在STM32中UART具有DMA功能,并且收发都可以使用DMA,使用DMA发送基本上大家不会遇到什么问题,因为发送的时候会告知DMA发送的数据长度,DMA按照发送的长度直接发送就OK了,但是使用DMA接收时候就不同了,因为有时候数据接收并不是每一次都是定长的,但是DMA只在接收数据长度和设定数据长度相同的时候才可以触发中断,告诉MCU数据接收完毕,针对这个问题,解决方法如下,有一点复杂,但是很管用。

1、 首先了解串口通信的协议



(原文件名:1.jpg)

从上图可知,UART在传输一个字节的时候,首先拉低,传输起始位,然后在是LSB –MSB,最后是停止位,停止位是高电平

2、 超时时间

搞过串口通信的都知道,如果串口有协议,一般都是有个超时时间的,超时时间是定义两个帧之间的间隔的,如果串口接收到一个字节后,在规定的超时时间内没有接收到其他数据,我们则认为前面接收的数据位一帧。



(原文件名:2.jpg)

3、 定时器复位复位模式

STM32定时器功能比较强大,其中有一种模式为复位模式,



(原文件名:3.jpg)

上图STM32 用户手册中的举例,注意红色箭头指向的位置,TI1的输入上升沿会复位定时器的计数器,具体请查阅STM32用户手册关于这部分的描述。

整体的思路是这样的,一开始设置好DMA接收,可以把缓冲区长度设置为帧最大长度,我们可以把RX连接到定时器的管脚输入端,并且一开始设置输入并且使能引脚下降沿中断,当帧的第一个字节发送时,因为起始位为低电平,空闲时UART为高电平,满足条件,进入中断,禁止中断,并且在中断中开启定时器,该定时器工作在复位模式,上升沿复位,并且设置好定时器输出比较值为超时时间,比如20ms,这样,在传输后面字节时,肯定会有高低电平出现,即便是传输的是0x00,0xFF,虽然UART数据区不变,但是都为1,或都为0,但是因为起始位为低电平,停止位是高电平,所以肯定会有上升沿,定时器会一直复位,输出定时器的计数器一直到达不了输出比较值,当一帧传输结束后,定时在最后一个字节复位后,由于没有数据继续到达,无法复位,则计数器就能计到输出比较值,这时发出中断,在定时器中断中可以计算出接收数据的长度,并且通知外部数据已经接收完毕。

4、 功能实现

实现的步骤:

1、硬件连接:UART的RX线在连接外部的同时,还需要连接到一个定时器的输入端TIMx_CHx,定时器可以为任意定时器,但是CHx,只能为CH1或CH2,具体的需要看STM32的定时器逻辑图,以STM32F101CB为例,我们暂定把UART1的RX在连接RS232的同时,还连接到TIM4_CH2。

2、软件设置

a) IO、中断设置:在把UART功能口设置好后,还需要设置TIM4_CH2为输入上拉,并且使能该引脚外部中断

/**

* @brief Configures the different GPIO ports.

* @param None

* @retval : None

*/

void GPIO_Configuration(void)

{

/* Configure USART1_Rx as input floating */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure USART1_Tx as alternate push-pull */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOB.7 Configuration: TIM4 Channel2 as input floatinng */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING ;

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

/**

* @brief Configures the system EXIT.

* @param None

* @retval None

*/

void EXTI_Configuration(void)

{

EXTI_InitTypeDef EXTI_InitStructure;

/* Connect EXTI8 Line to PB.07 pin */

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource7);

/* Configure EXTI8 line */

EXTI_InitStructure.EXTI_Line = EXTI_Line7;

EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;

EXTI_InitStructure.EXTI_LineCmd = ENABLE;

EXTI_Init(&EXTI_InitStructure);

/* Clears EXTI line pending bits. */

EXTI_ClearITPendingBit(EXTI_Line7);

}

/**

* @brief Configures NVIC and Vector Table base location.

* @param None

* @retval : None

*/

void NVIC_Configuration(void)

{

NVIC_InitTypeDef NVIC_InitStructure;

/* Configure the NVIC Preemption Priority Bits*/

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

/* Set the Vector Table base location at 0x08000000 */

NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);

/* Enable the DMA1_Channel_Rx Interrupt */

NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

/* Configure DMA1_Channel_Tx interrupt */

NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

/* Configure TIM1 update interrupt */

NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

/* Enable and set EXTI9_5 Interrupt to the lowest priority */

NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 6;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

}

把DMA接收的数据缓冲区设置为你认为最大的帧长度,(如果最长不能确定,也可以随便指定一个长度,后面再讲怎么实现)。

b) 定时器设置

因为使用的是TIM4_CH2,所以需要配置TIM4,并且配置为复位模式,把超时时间定为20ms,为了方便TIM4时钟定输入为1KHZ

/*

* This function is called by timer_init() to perform the non-generic portion

* of the initialization of the timer module.

*/

void timer_init_non_generic(void)

{

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

TIM_OCInitTypeDef TIM_OCInitStructure;

TIM_ICInitTypeDef TIM_ICInitStructure;

/* TIM4 configuration ----------------------------------------------------*/

TIM_TimeBaseStructure.TIM_Period = 65535;

TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock/1000000 * 1000 - 1;

TIM_TimeBaseStructure.TIM_ClockDivision = 0;

TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

/* Output Compare Mode configuration: Channel1 */

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;

TIM_OCInitStructure.TIM_Pulse = 20;

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;

TIM_OC1Init(TIM4, &TIM_OCInitStructure);

/* TIM4 Channel 2 Input Capture Configuration */

TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;

TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;

TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;

TIM_ICInitStructure.TIM_ICFilter = 0;

TIM_ICInit(TIM4, &TIM_ICInitStructure);

/* TIM4 Input trigger configuration: External Trigger connected to TI2 */

TIM_SelectInputTrigger(TIM4, TIM_TS_TI2FP2);

/* TIM4 configuration in slave reset mode where the timer counter is

re-initialied in response to rising edges on an input capture (TI2) */

TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset);

/* TIM4 IT CC1 enable */

TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);

}

c)工作过程如下

在串口传输起始位的时候,首先产生外部中断,在外部中断中开启定时器,禁止外部中断,只要串口上一直有数据,定时器肯定会不停的复位,到达不了定时时间,当串口上没有数据的时候,到超时时间后,定时器产生中断,此时可以读出接收的数据长度,然后开启外部中断,进入下一个周期。



(原文件名:4.jpg)

总结:本方法的缺点是程序开始的初始化麻烦些,但是优点是非常明显的,彻底解放了CPU,这样在计算串口超时的时候,就不需要定时器不停的中断,并且串口接收数据使用DMA方式,也不需要CPU参与,只是在接收结束的时候通知CPU取数据,CPU的利用率会更高。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: