Основная проблема вашего решения заключается в том, что вы выбираете биты в точке перехода, а не в центре битов.При обнаружении перехода START вы задерживаете только один битовый период, поэтому сэмплируйте r1
на битовом переходе, а не на бите center - это почти наверняка приведет к ошибкам, особенно на высокой скорости, где краяможет быть не очень быстроПервая задержка должна быть длиной 1,5 бита.(delay_time * 2 / 3
), как показано ниже:
Вторая проблема заключается в том, что вы излишне задерживаетесь после бита STOP, что приведет к пропускуследующий переход START, потому что это может произойти до того, как вы сбросите флаг прерывания.Ваша работа будет завершена, как только вы получите r8
.
Выборка r0
и r9
не имеет смысла, вы их ни в коем случае не отбрасываете, а состояние r0
подразумевается в любом событии формыEXTI-переход и r9
не будут равны 1, если отправитель генерирует недопустимые кадры.Более того, если вы не делаете выборку r9
, задержка перед ней также становится ненужной.Эти строки должны быть удалены:
delay_us(delay_time);
r9 = GPIOA->IDR;
delay_us(delay_time);
Это, по крайней мере, даст вам два битных периода, когда ваш процессор может выполнять другую работу, кроме того, что застревает в контексте прерывания, но задержка является обработчиком прерывания, что не является хорошей практикой.- он блокирует выполнение нормального кода и все прерывания с более низким приоритетом, что делает решение непригодным для систем реального времени.В этом случае, если soft-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 работает на частоте 120 МГц при 4800 и увеличивает его до 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, но если вы не собираетесь обрабатывать такие ошибки в любом случае, проверка не имеет смысла.