您的解决方案的根本问题是您在转换点而不是位中心对位进行采样。在检测到 START 转换时,您只延迟一个位周期,因此在位转换而不是位 中心 处采样 r1 - 这几乎肯定会导致错误,尤其是在高速时边缘可能不是很快。第一个延迟应该是 1.5 位周期长。 (delay_time * 2 / 3) 如下图:
第二个问题是您在 STOP 位之后不必要地延迟,这将导致您错过下一个 START 转换,因为它可能发生在您清除中断标志之前。只要你有r8,你的工作就完成了。
采样r0 和r9 在任何情况下都没有任何用处,您在任何情况下都丢弃它们,并且状态r0 在任何事件中都隐含在EXTI 转换中,并且r9 仅在发送者是生成无效帧。此外,如果您没有对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,它可能会产生帧错误,但如果您无论如何都不打算处理此类错误,则检查没有任何目的。