STM32上的USART接收器

Vai*_*hav 0 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 ;}
Run Code Online (Sandbox Code Playgroud)

谢谢你的帮助。

Cli*_*ord 6

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

在此处输入图片说明

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

采样r0r9无济于事你丢弃他们在任何情况下,国家r0是隐含在任何情况下形式EXTI过渡,而r9只会不是1,如果发件人是产生无效帧。此外,如果您不采样r9延迟,则也无需延迟。这些行应删除:

delay_us(delay_time);
r9 = GPIOA->IDR;
delay_us(delay_time);
Run Code Online (Sandbox Code Playgroud)

这至少会给您两个比特周期,让您的处理器除了陷在中断上下文之外还可以做其他工作,但是延迟是中断处理程序不是一个好习惯-它会阻止正常代码的执行以及所有较低优先级的中断,从而导致了解决方案不适合实时系统。在这种情况下,如果软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 ;
}
Run Code Online (Sandbox Code Playgroud)

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

我已经在STM32上成功使用了此技术,该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 ;
    }
}
Run Code Online (Sandbox Code Playgroud)

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