STM32上的USART接收器

时间:2019-03-20 08:07:48

标签: c embedded stm32 usart

您好,我目前正在进行USART通信,试图从任何GPIO引脚发送和接收数据。

在接收时,我成功地以任何波特率传输数据。

我一次可以收到一个字符。该引脚通过RX引脚设置为外部下降沿中断。

但是,当我从终端向控制器传输类似“ test”的字符串时,仅收到“ t”,其余3个字符为垃圾值。我当时在想,在收到第一个字符并将其保存后,不会很快触发下一个字符的中断。

此示例代码中的许多内容都经过硬编码,用于测试。

这里是接收器的示例代码

void EXTI0_IRQHandler(void){
r0 = GPIOA->IDR;
delay_us(delay_time);
r1 = GPIOA->IDR;
delay_us(delay_time);
r2 = GPIOA->IDR;
delay_us(delay_time);
r3 = GPIOA->IDR;
delay_us(delay_time);
r4 = GPIOA->IDR;
delay_us(delay_time);
r5 = GPIOA->IDR;
delay_us(delay_time);
r6 = GPIOA->IDR;
delay_us(delay_time);
r7 = GPIOA->IDR;
delay_us(delay_time);
r8 = GPIOA->IDR;
delay_us(delay_time);
r9 = GPIOA->IDR;
delay_us(delay_time);
r1 = r1 & 0x00000001;
r2 = r2 & 0x00000001;
r3 = r3 & 0x00000001;
r4 = r4 & 0x00000001;
r5 = r5 & 0x00000001;
r6 = r6 & 0x00000001;
r7 = r7 & 0x00000001;
r8 = r8 & 0x00000001;
x |= r8;
x = x << 1;
x |= r7;
x = x << 1;
x |= r6;
x = x << 1;
x |= r5;
x = x << 1;
x |= r4;
x = x << 1;
x |= r3;
x = x << 1;
x |= r2;
x = x << 1;
x |= r1;
buff1[z++] = x;
EXTI->PR |= 0X00000001;
x=0;
return ;}

感谢您的帮助。

3 个答案:

答案 0 :(得分:5)

解决方案的根本问题是,您是在过渡点而不是位中心对位进行采样。在检测到START跳变时,您仅延迟了一个位周期,因此在位跳变而不是位 centre 处采样r1-这几乎肯定会导致错误,尤其是在高速下边缘可能不会很快。第一延迟应为1.5位周期长。 (delay_time * 2 / 3),如下所示:

enter image description here

第二个问题是您在STOP位之后不必要地延迟,这将导致您错过下一个START转换,因为它可能在清除中断标志之前发生。 r8一经完成,您的工作就完成了。

r0r9进行采样无济于事,无论如何在任何情况下都将其丢弃,并且状态r0在任何情况下都属于EXTI过渡,因此r9会隐含如果发件人生成无效帧,则不为1。此外,如果您不对r9进行采样,那么也不需要延迟。这些行应删除:

delay_us(delay_time);
r9 = GPIOA->IDR;
delay_us(delay_time);

这至少会给您两个位周期,让您的处理器除了陷在中断上下文之外还可以做其他工作,但是延迟是中断处理程序不是一个好习惯-它会阻止正常代码的执行和所有优先级较低的中断使该解决方案不适合实时系统。在这种情况下,如果软UART Rx是系统要做的全部工作,则您可以通过简单地轮询GPIO而不是使用中断来获得更好的结果-至少其他中断可以正常运行,并且实现起来容易得多

您的“展开循环”实现也没有真正的目的,即存在延迟-即使在非常高的比特率下,循环开销在帧持续时间内也可能是微不足道的,如果可以的话,您可以对其进行调整延迟一点补偿:

void EXTI0_IRQHandler(void)
{
    delay_us(delay_time * 2 / 3);
    for( int i = 7; i >= 0; i-- )
    {
        x |= GPIOA->IDR << i ;
        delay_us(delay_time);
    }

    EXTI->PR |= 0X00000001;
    buff1[z++] = x;
    x = 0 ;
    return ;
}

一个对软接收器更健壮的解决方案,可以与系统中的其他进程配合使用,应该只使用EXTI中断来检测起始位;处理程序应禁用EXTI,并以波特率加半个周期启动定时器。计时器的中断处理程序在位周期的中央对GPIO引脚进行采样,并在EXTI之后的第一个中断时将周期更改为一个位周期。对于每个定时器中断,当它禁用定时器并为下一个起始位重新启用EXTI时,它将采样并计数这些位,直到移入整个数据字为止。

我已经在STM32上成功使用了这项技术,该技术在4800上运行于120MHz,并将其推到38400,但是在中断上下文中,它以每秒26微秒的速度变得很忙,所以您的应用程序可能还有其他事情要做?

以下是我的实现的一个泛化版本。它使用STM32标准外围设备库调用,而不是直接寄存器访问或更高版本的STM32Cube HAL,但是您可以根据需要轻松地以一种或另一种方式移植它。帧是N,8,1。

#define SOFT_RX__BAUD = 4800u ;
#define SOFT_RX_TIMER_RELOAD = 100u ;

void softRxInit( void )
{
    // Enable SYSCFG clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

    // Connect the EXTI Line to GPIO Pin
    SYSCFG_EXTILineConfig( EXTI_PortSourceGPIOB, EXTI_PinSource0 );

    TIM_Cmd( TIM10, DISABLE);

    // NVIC initialisation
    NVIC_InitTypeDef NVIC_InitStructure = {0,0,0,DISABLE};
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_TIM10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12; 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // Enable peripheral clock to timers
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10, ENABLE);

    TIM_ARRPreloadConfig( TIM10, DISABLE );

    // Generate soft Rx rate clock (4800 Baud)
    TIM_TimeBaseInitTypeDef init = {0};
    TIM_TimeBaseStructInit( &init ) ;
    init.TIM_Period = static_cast<uint32_t>( SOFT_RX_TIMER_RELOAD );
    init.TIM_Prescaler = static_cast<uint16_t>( (TIM10_ClockRate() / (SOFT_RX__BAUD * SOFT_RX_TIMER_RELOAD)) - 1 );
    init.TIM_ClockDivision = 0;
    init.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit( TIM10, &init ) ;

    // Enable the EXTI Interrupt in the NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init( &NVIC_InitStructure );

    // Dummy call to handler to force initialisation 
    // of UART frame state machine
    softRxHandler() ;
}

// Soft UART Rx START-bit interrupt handler
void EXTI0_IRQHandler()
{
    // Shared interrupt, so verify that it is the correct one
    if( EXTI_GetFlagStatus( EXTI_Line0 ) == SET )
    {
        // Clear the EXTI line pending bit.
        // Same as EXTI_ClearITPendingBit( EXTI_Line11 )
        EXTI_ClearFlag( EXTI_Line0 ) ;

        // Call Soft UART Rx handler
        softRxHandler() ;
    }
}

void TIM1_UP_TIM10_IRQHandler( void )
{
    // Call Soft UART Rx handler
    softRxHandler() ;

    TIM_ClearITPendingBit( TIM10, TIM_IT_Update );
}

// Handler for software UART Rx
inline void softRxHandler()
{
    static const int START_BIT = -1 ;
    static const int STOP_BIT = 8 ;
    static const int HALF_BIT = SOFT_RX_TIMER_RELOAD / 2;
    static const int FULL_BIT = SOFT_RX_TIMER_RELOAD ;
    static int rx_bit_n = STOP_BIT ;
    static const uint8_t RXDATA_MSB = 0x80 ;
    static uint8_t rx_data = 0 ;
    static EXTI_InitTypeDef extiInit = { EXTI_Line0,
                                         EXTI_Mode_Interrupt,
                                         EXTI_Trigger_Falling,
                                         DISABLE } ;

    // Switch START-bit/DATA-bit
    switch( rx_bit_n )
    {
        case START_BIT :
        {
            // Stop waiting for START_BIT
            extiInit.EXTI_LineCmd = DISABLE;
            EXTI_Init( &extiInit );

            // Enable the Interrupt
            TIM_ClearITPendingBit( TIM10, TIM_IT_Update );
            TIM_ITConfig( TIM10, TIM_IT_Update, ENABLE );

            // Enable the timer (TIM10)
            // Set time to hit centre of data LSB
            TIM_SetAutoreload( TIM10, FULL_BIT + HALF_BIT ) ;
            TIM_Cmd( TIM10, ENABLE );

            // Next = LSB data
            rx_data = 0 ;
            rx_bit_n++ ;
        }
        break ;

        // STOP_BIT is only set on first-time initialisation as a state, othewise it is
        // transient within this scase.
        // Use fall through and conditional test to allow
        // case to handle both initialisation and UART-frame (N,8,1) restart.
        case STOP_BIT :
        default :   // Data bits
        {
            TIM_ClearITPendingBit( TIM10, TIM_IT_Update );

            if( rx_bit_n < STOP_BIT )
            {
                if( rx_bit_n == 0 )
                {
                    // On LSB reset time to hit centre of successive bits
                    TIM_SetAutoreload( TIM10, FULL_BIT ) ;
                }

                // Shift last bit toward LSB (emulate UART shift register)
                rx_data >>= 1 ;

                // Read Rx bit from GPIO
                if( GPIO_ReadInputDataBit( GPIOB, GPIO_Pin_0 ) != 0 )
                {
                    rx_data |= RXDATA_MSB ;
                }

                // Next bit
                rx_bit_n++ ;
            }

            // If initial state or last DATA bit sampled...
            if( rx_bit_n == STOP_BIT )
            {
                // Stop DATA-bit sample timer
                TIM_Cmd( TIM10, DISABLE );

                // Wait for new START-bit
                rx_bit_n = START_BIT ;
                extiInit.EXTI_LineCmd = ENABLE;
                EXTI_Init( &extiInit );

                // Place character in Rx buffer
                serialReceive( rx_data ) ;
            }
        }
        break ;
    }
}

代码的工作方式与实际UART相同,如上面的时序图所示,不同之处在于在我的实现中STOP位实际上没有被采样-这是不必要的。它仅用于确保随后的START位是1-> 0的跳变,通常可以忽略。真实的UART如果不为1,则可能会生成成帧错误,但是,如果您无论如何都不会处理此类错误,则没有任何检查目的。

答案 1 :(得分:0)

我看不到您的代码中考虑了通常是串行传输一部分的起始位。您似乎只在寻找8个数据位和一个停止位。

按照“起始位是终止位的倒数”的约定,您的代码将在字符之间增加一条边沿,从而显然会将您检测到的位流移位一位。

答案 2 :(得分:0)

您提到发送字符串“ test”时收到字符“ t”。 在字符串中引入足够的字符间延迟。 希望它能工作。 您可以使用Docklite发送具有字符间延迟的字符串。