简体   繁体   English

STM32 上的 USART 接收器

[英]USART receiver on STM32

Hi I am currently working on USART communication trying to transmit and receive data from any GPIO pin.嗨,我目前正在研究 USART 通信,尝试从任何 GPIO 引脚传输和接收数据。

I am succeed to transmit data at any baud-rate when it comes to receiving i got stuck at a point.我在接收时成功地以任何波特率传输数据,我卡在了某个点上。

I was able to receive a character at a time.我能够一次接收一个角色。 Pin is set as external falling edge interrupt used a RX pin.引脚设置为使用 RX 引脚的外部下降沿中断。

But when i transmit a string like "test" from terminal to controller only "t" is received rest 3 character is garbage value.但是当我将像“test”这样的字符串从终端传输到控制器时,只接收到“t”,其余 3 个字符是垃圾值。 I was thinking that after receiving first character and saving it, the Interrupt is not triggered as fast for next character.我在想,在接收到第一个字符并保存后,下一个字符的中断不会被触发得那么快。

Many things are hard coded in this sample code for test purpose.出于测试目的,此示例代码中对许多内容进行了硬编码。

Here the sample code for receiver这里是接收器的示例代码

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 ;}

Thanks for your help.谢谢你的帮助。

The fundamental problem with your solution is that you are sampling the bits at the transition point rather then the bit centre.您的解决方案的基本问题是您在转换点而不是位中心对位进行采样。 On detection of the START transition, you delay one bit period only, so sample r1 at the bit transition rather then the bit centre - this will almost certainly result in errors, especially at high speed where the edges may not be very fast.在检测到 START 转换时,您只延迟一个位周期,因此在位转换而不是位中心采样r1 - 这几乎肯定会导致错误,特别是在边缘可能不是很快的高速下。 The first delay should be 1.5 bit periods long.第一个延迟应该是 1.5 位周期长。 ( delay_time * 2 / 3 ) as illustrated below: ( delay_time * 2 / 3 ) 如下图所示:

在此处输入图片说明

A second problem is that you unnecessarily delay after the STOP bit, which will cause you to miss the next START transition because it may occur before you clear the interrupt flag.第二个问题是您在 STOP 位之后不必要地延迟,这将导致您错过下一个 START 转换,因为它可能在您清除中断标志之前发生。 Your work is done as soon as you have r8 .一旦您拥有r8您的工作就完成了。

Sampling r0 and r9 serves no purpose you discard them in any case, and the state r0 is implicit in any event form the EXTI transition, and r9 would only not be 1 if the sender was generating invalid frames.采样r0r9没有任何意义,你在任何情况下都丢弃它们,并且状态r0在任何形式的 EXTI 转换中都是隐含的,如果发送方生成无效帧,则r9不会为 1。 Moreover if you are not sampling r9 the delay before it also becomes unnecessary.此外,如果您不对r9进行采样,那么它之前的延迟也变得不必要了。 These lines should be removed:应删除这些行:

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

That would at least give you two bit periods where your processor could do other work other then being stuck in the interrupt context, but delaying is an interrupt handler is not good practice - it blocks execution of normal code and all lower priority interrupts making the solution unsuited to real-time systems.这至少会给你两个位周期,你的处理器可以做其他工作,而不是卡在中断上下文中,但延迟是一个中断处理程序不是一个好习惯 - 它阻止执行正常代码和所有较低优先级的中断,从而解决问题不适合实时系统。 In this case if the soft-UART Rx is all the system has to do, you are likely to get better results by simply polling the GPIO rather then using interrupts - at least then other interrupts could run normally, and it is much simpler to implement.在这种情况下,如果系统只需要软 UART Rx,您可能会通过简单地轮询 GPIO 而不是使用中断来获得更好的结果 - 至少其他中断可以正常运行,并且实现起来要简单得多.

Your "unrolled-loop" implementation also serves no real purpose with the delays in place - even at very high bit rates a loop overhead is likely to be insignificant over the duration of the frame, and if it were you could tweak the delays a little to compensate:您的“展开循环”实现在延迟到位时也没有实际用途 - 即使在非常高的比特率下,循环开销在帧持续时间内也可能微不足道,如果是这样,您可以稍微调整延迟补偿:

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 ;
}

A more robust solution for a soft receiver that will play well with other processes in your system, should use the EXTI interrupt only to detect the start bit;一个更健壮的软接收器解决方案可以很好地与系统中的其他进程一起使用,应该只使用 EXTI 中断来检测起始位; the handler should disable the EXTI, and start a timer at the baud rate plus half a bit period.处理程序应禁用 EXTI,并以波特率加半位周期启动计时器。 The interrupt handler for the timer, samples the GPIO pin at the centre of the bit period, and on the first interrupt after the EXTI, changes the period to one bit period.定时器的中断处理程序在位周期的中心对 GPIO 引脚进行采样,并在 EXTI 之后的第一个中断时将周期更改为一个位周期。 For each timer interrupt it samples and counts the bits until a whole data word has been shifted in, when it disables the timer and re-enables the EXTI for the next start bit.对于每个定时器中断,它都会对位进行采样和计数,直到整个数据字被移入,此时它会禁用定时器并重新启用下一个起始位的 EXTI。

I have successfully used this technique on STM32 running at 120MHz at 4800 and pushed it to 38400, but at 26 microseconds per bit it gets quite busy in the interrupt context, and your application presumably has other things to do?我已经成功地在 STM32 上以 4800 频率运行于 120MHz 并将其推到了 38400,但在每比特 26 微秒时,它在中断上下文中变得非常繁忙,您的应用程序可能还有其他事情要做?

The following is a slightly genericised version of my implementation.以下是我的实现的稍微通用化的版本。 It uses STM32 Standard Peripheral Library calls rather then direct register access or the later STM32Cube HAL, but you can easily port it one way or the other as you need.它使用 STM32 标准外设库调用,而不是直接寄存器访问或后来的 STM32Cube HAL,但您可以根据需要轻松地以一种或另一种方式移植它。 The framing is N,8,1.帧是 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 ;
    }
}

The code works in the same way as a real UART as illustrated in the timing diagrem above with the exception that in my implementation the STOP bit is not actually sampled - it is unnecessary;代码的工作方式与上面的时序图所示的真实 UART 相同,但在我的实现中,STOP 位实际上并未被采样——这是不必要的; it only serves to ensure that the subsequent START bit is a 1 -> 0 transition and can generally be ignored.它仅用于确保后续的 START 位是 1 -> 0 转换,通常可以忽略。 A real UART would probably generate a framing error if it were not 1, but if you were not going to handle such errors in any event, there is no purpose in checking.如果不是 1,真正的 UART 可能会产生帧错误,但如果您无论如何都不打算处理此类错误,则检查没有任何意义。

I can't see in your code where you take account of the start bit that is normally part of a serial transmission.我在您的代码中看不到您在何处考虑了通常是串行传输一部分的起始位 You seem to be only looking for 8 data bits and a stop bit.您似乎只在寻找 8 个数据位和一个停止位。

With the convention of "start bit is the inverse of stop bit" there will be an additional edge your code detects between characters, thus apparently shifting the bit stream you detect by one bit.根据“起始位是停止位的倒数”的约定,您的代码将在字符之间检测到一个额外的边缘,因此显然将您检测到的位流移动了一位。

You mentioned that character 't' is received when string "test" is sent.您提到发送字符串“test”时会收到字符“t”。 Introduce sufficient inter character delay in the string.在字符串中引入足够的字符间延迟。 Hopefully it works.希望它有效。 You can use docklite for sending string with inter character delay.您可以使用docklite 发送具有字符间延迟的字符串。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM