From 666ae736ef603fee8ceb1416089fdaf9b3146ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Sat, 8 May 2021 10:34:15 +0200 Subject: [PATCH] application: add UART autodetection (copied from BusVoodoo) --- application.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 317 insertions(+), 2 deletions(-) diff --git a/application.c b/application.c index ef594cc..52f6478 100644 --- a/application.c +++ b/application.c @@ -27,6 +27,7 @@ #include // flash utilities #include // ADC utilities #include // timer library +#include // universal synchronous asynchronous receiver transmitter library /* own libraries */ #include "global.h" // board definitions @@ -35,6 +36,7 @@ #include "usb_cdcacm.h" // USB CDC ACM utilities #include "terminal.h" // handle the terminal interface #include "menu.h" // menu utilities +#include "usart_enhanced.h" // USART utilities got frame checking /** watchdog period in ms */ #define WATCHDOG_PERIOD 10000 @@ -561,7 +563,7 @@ void TIM_ISR(FREQUENCY_TIMER)(void) } } -/** monitor single channel for activity +/** monitor single channel for activity and measure frequency * @param[in] argument channel number */ static void command_monitor_single(void* argument) @@ -685,6 +687,7 @@ static void command_monitor_single(void* argument) // clean up gpio_set(GPIO_PORT(SHIFT_EN_PIN), GPIO_PIN(SHIFT_EN_PIN)); // remove power from level shifters pull-up + gpio_mode_setup(GPIO_PORT(UART_RX), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(UART_RX)); // put pin back to safe floating mode mux_select(-1); // disable multiplexer timer_disable_counter(TIM(MONITOR_TIMER)); // disable timer rcc_periph_reset_pulse(RST_TIM(MONITOR_TIMER)); // reset timer state @@ -698,6 +701,310 @@ static void command_monitor_single(void* argument) rcc_periph_clock_disable(RCC_TIM(FREQUENCY_TIMER)); // disable clock for timer peripheral } +/** the possible properties of a UART configuration (to be updated with every character) */ +struct uart_configuration_t { + uint8_t databits; /**< data word size in bits */ + bool databits_matching; /**< if the data is still matching the data bits */ + bool parity_even; /**< if the date is still matching the additional even parity bit */ + bool parity_odd; /**< if the date is still matching the additional odd parity bit */ + bool parity_mark; /**< if the date is still matching the additional mark parity bit */ + bool parity_space; /**< if the date is still matching the additional space parity bit */ + uint8_t parity_possibilities; /**< the number to still matching parity possibilities (number of parity_* at true) */ +}; + +/** reset all matching values of UART configuration + * @param[out] configuration UART configuration to reset + */ +static void uart_configuration_reset(struct uart_configuration_t* configuration) +{ + configuration->databits_matching = true; + configuration->parity_even = true; + configuration->parity_odd = true; + configuration->parity_mark = true; + configuration->parity_space = true; + configuration->parity_possibilities = 4; +} + +/** autodetect UART configuration on single channel + * @param[in] argument channel number + */ +static void command_uart_autodetect(void* argument) +{ + (void)argument; // we won't use the argument + + // get input channel + if (NULL == argument) { + puts("provide channel to monitor for UART activity\n"); + return; + } + const uint32_t channel = *(uint32_t*)argument; // get channel argument + if (!(channel < CHANNEL_NUMBERS)) { // verify argument + printf("channel %u out of range (0-%u)\n", channel, CHANNEL_NUMBERS - 1); + return; + } + + // verify target voltage is OK + const float* voltages = measure_voltages(); // get target voltage + if (voltages[1] < 1.5) { + puts("target voltage too low: "); + print_fpu(voltages[1], 2); + puts(" < 1.5V\n"); + return; + } else { + puts("target voltage: "); + print_fpu(voltages[1], 2); + puts("V\n"); + } + + // setup USART to receive character + uint8_t uart_databits = 8; // start with 8 bits since this is the most common case (i.e. no additional parity bit is used) + rcc_periph_clock_enable(RCC_USART(UART_ID)); // enable USART peripheral + rcc_periph_reset_pulse(RST_USART(FREQUENCY_TIMER)); // reset USART peripheral + usart_set_baudrate(USART(UART_ID), 1200); // configure UART to slowest baud rate + usart_set_databits(USART(UART_ID), uart_databits); // configure UART to pre-selected data-bits + usart_set_stopbits(USART(UART_ID), USART_STOPBITS_1); // 1 stop-bits also complies to 2 stop-bits + usart_set_parity(USART(UART_ID), USART_PARITY_NONE); // get the raw data since we will do the parity check ourselves + usart_set_mode(USART(UART_ID), USART_MODE_RX); // we will only receive data + USART_CR3(USART(UART_ID)) |= USART_CR3_HDSEL; // we will use the half-duplex mode to use the TX pin as RX + // USART will be enabled by the autodetection loop + + // select channel + rcc_periph_clock_enable(GPIO_RCC(UART_RX)); // enable clock for USART RX pin port peripheral + gpio_mode_setup(GPIO_PORT(UART_RX), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(UART_RX)); // use as input for the timer (it is pulled up by level shifter) + gpio_set_af(GPIO_PORT(UART_RX), FREQUENCY_AF, GPIO_PIN(UART_RX)); // set alternate function to timer channel + rcc_periph_clock_enable(GPIO_RCC(UART_TX)); // enable clock for USART TX pin port peripheral + gpio_mode_setup(GPIO_PORT(UART_TX), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(UART_TX)); // use pin for UART data (normally output, but in half duplex it can become input) + gpio_set_af(GPIO_PORT(UART_TX), UART_AF, GPIO_PIN(UART_TX)); // set alternate function to UART + mux_select(channel); // select channel + gpio_clear(GPIO_PORT(SHIFT_EN_PIN), GPIO_PIN(SHIFT_EN_PIN)); // connect target voltage to level shifters pull-up + + // show help + printf("CH%02u is pulled to target voltage by 10 kOhm\n", channel); + puts("high = 1.5-5.5V, data is shown as decoded\n"); + puts("UART configuration autodetection improves with incoming data\n"); + puts("press any key to stop autodetection\n"); + + // setup timer to measure frequency + rcc_periph_clock_enable(RCC_TIM(FREQUENCY_TIMER)); // enable clock for timer peripheral + rcc_periph_reset_pulse(RST_TIM(FREQUENCY_TIMER)); // reset timer state + timer_disable_counter(TIM(FREQUENCY_TIMER)); // disable timer to configure it + timer_set_mode(TIM(FREQUENCY_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up + timer_set_prescaler(TIM(FREQUENCY_TIMER), 0); // don't use prescale so to get the most precise measurement + timer_ic_set_input(TIM(FREQUENCY_TIMER), TIM_IC(FREQUENCY_CHANNEL), TIM_IC_IN_TI(FREQUENCY_CHANNEL)); // configure the input capture ICx to use the right channel TIn + timer_ic_set_filter(TIM(FREQUENCY_TIMER), TIM_IC(FREQUENCY_CHANNEL), TIM_IC_OFF); // use no filter input to keep precise timing + timer_ic_set_polarity(TIM(FREQUENCY_TIMER), TIM_IC(FREQUENCY_CHANNEL), TIM_IC_FALLING); // capture on falling edge + timer_ic_set_prescaler(TIM(FREQUENCY_TIMER), TIM_IC(FREQUENCY_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse + timer_ic_enable(TIM(FREQUENCY_TIMER), TIM_IC(FREQUENCY_CHANNEL)); // enable capture interrupt + timer_clear_flag(TIM(FREQUENCY_TIMER), TIM_SR_CCIF(FREQUENCY_CHANNEL)); // clear input compare flag + timer_enable_irq(TIM(FREQUENCY_TIMER), TIM_DIER_CCIE(FREQUENCY_CHANNEL)); // enable capture interrupt + timer_update_on_overflow(TIM(FREQUENCY_TIMER)); // only use counter overflow as UEV source (use overflow to measure longer times) + timer_clear_flag(TIM(FREQUENCY_TIMER), TIM_SR_UIF); // clear overflow flag + timer_enable_irq(TIM(FREQUENCY_TIMER), TIM_DIER_UIE); // enable update interrupt for timer + nvic_enable_irq(NVIC_TIM_IRQ(FREQUENCY_TIMER)); // catch interrupts for this timer + pulse_duration = UINT32_MAX; // reset pulse duration + timer_enable_counter(TIM(FREQUENCY_TIMER)); // enable timer + + // start autodetection + bool reset_state = true; // flag to know if we need to reset the states + uint8_t rx_errors; // number of UART receive errors received + bool wait_for_idle = false; // flag to wait for an IDLE frame + /** the possible UART configurations + * @note since the first valid configuration will be chosen, order in decreasing probability of being valid and decreasing probability or getting invalidated */ + struct uart_configuration_t uart_configurations[] = { + { .databits = 5 }, + { .databits = 6 }, + { .databits = 8 }, + { .databits = 7 }, + }; + uint8_t uart_configuration_valid = LENGTH(uart_configurations); // current best valid UART configuration index + char uart_configuration_parity = '?'; // current best valid UART parity + const uint32_t baudrates[] = { 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 576000, 921600, 1000000 }; // list of standard baud rates, to match with measured frequency + uint32_t uart_baudrate = 0; // fastest found baud rate + while (!user_input_available) { // run until user breaks it + // time to do periodic checks + if (wakeup_flag || second_flag) { + iwdg_reset(); // kick the dog + wakeup_flag = false; // clear flag + second_flag = false; // clear flag + } + if (reset_state) { // reset the configuration + rx_errors = 0; + for (uint8_t i = 0; i < LENGTH(uart_configurations); i++) { + uart_configuration_reset(&uart_configurations[i]); + } + usart_recv(USART(UART_ID)); // clear input buffer and allow flag to be set + usart_enable(USART(UART_ID)); // ensure UART is enabled + reset_state = false; + } + if (pulse_flag) { // new pulse duration has been measured + pulse_flag = false; // clear flag + uint32_t baudrate = rcc_ahb_frequency / (pulse_duration / 2); // calculate baud rate based on measured timing + if (baudrate > uart_baudrate + 100) { // new higher baud rate detected + uart_baudrate = baudrate; // save new baud rate + if (uart_baudrate >= 1200) { // ensure minimum hardware supported baud rate is respected + // search for closest standard baud rate + uint32_t standard_baudrate = 0; + for (uint8_t i = 0; i < LENGTH(baudrates); i++) { + if (uart_baudrate >= baudrates[i] * 0.9 && uart_baudrate <= baudrates[i] * 1.1) { // measured baud rate matches standard baud rate within factor + standard_baudrate = baudrates[i]; // remember matching baud rate + break; // stop searching for matching baud rate + } + } + if (standard_baudrate) { // matching standard baud rate found + uart_baudrate = standard_baudrate; // save matching baud rate + } + usart_disable(USART(UART_ID)); // disable UART before reconfiguring + usart_set_baudrate(USART(UART_ID), uart_baudrate); // set new baud rate + reset_state = true; // reset the states since we set a new baud rate + printf("\nnew baud rate: %u bps\n", uart_baudrate); // show measurement frequency + } else { + printf("\ndetected %u bps baud rate is lower than minimum supported 1200 bps\n", baudrate); + } + } + } + if (USART_SR(USART(UART_ID)) & (USART_SR_NE|USART_SR_FE)) { // error on UART received + usart_recv(USART(UART_ID)); // clear input buffer and flags + rx_errors++; // increment number of errors + if (rx_errors >= 5) { // the format seems wrong + // the threshold must be high enough so the UART peripheral has enough opportunities to synchronize to the start bit (just after an idle frame) + // too high frame error causes: + // - when set to 9 data-bits with high speed 8 data-bits traffic incoming: the next start bit comes right after the stop bit of and 8-bit frame, which is interpreted as faulty 9 data-bits frame stop bit + // - when set to 8 data-bits with 9 data-bits (8+1 parity) traffic incoming: the low parity bit is interpreted as faulty stop-bit + uart_databits = ((8 == uart_databits) ? 9 : 8); // switch between 8 and 9-bit packets + usart_disable(USART(UART_ID)); // disable UART before reconfiguring + usart_set_databits(USART(UART_ID), uart_databits); // set new data width + reset_state = true; + pulse_duration = UINT32_MAX; // also reset the baud rate + uart_baudrate = 0; // also reset the baud rate + rx_errors = 0; // reset error counter + printf("\nrestarting guessing because detected too many errors\n"); + } else { + wait_for_idle = true; // wait form an IDLE frame so to better sync to the next start bit + } + } + if (wait_for_idle) { + /* we have to check the IDLE flag in the main loop instead of just looping over the flag because a hardware fault could prevent it from being set. + */ + if (USART(UART_ID) | USART_SR_IDLE) { // idle flag set + wait_for_idle = false; // no need to wait anymore + } + if (USART_SR(USART(UART_ID)) | USART_SR_RXNE) { // data is available + USART_DR(USART(UART_ID)); // empty receive buffer so the IDLE flag can retrigger + } + } + if (USART_SR(USART(UART_ID)) & USART_SR_RXNE) { // data received + const uint16_t usart_data = usart_recv(USART(UART_ID)); // save received data (also clears flag) + if (0 == uart_baudrate) { // we did not find any valid baud rate yet + continue; + } + const uint16_t usart_data_padded = ((8 == uart_databits) ? usart_data | 0xff00 : usart_data | 0xfe00); // pad with 1 (stop bit/idle state) for better word size detection + const uint16_t usart_data_relevant = usart_data & ~(0xffff << uart_configurations[uart_configuration_valid].databits); // get only the data bits + // verify parity and word size + for (uint8_t i = 0; i < LENGTH(uart_configurations); i++) { + // skip check if we already know the word size is wrong + if (!uart_configurations[i].databits_matching) { + continue; + } + // do parity checks + if (uart_configurations[i].parity_even) { + uart_configurations[i].parity_even &= usart_enhanced_even_parity_lut[usart_data_relevant]; + } + if (uart_configurations[i].parity_odd) { + uart_configurations[i].parity_odd &= !usart_enhanced_even_parity_lut[usart_data_relevant]; + } + if (uart_configurations[i].parity_mark) { + uart_configurations[i].parity_mark &= (usart_data_padded & (1 << uart_configurations[i].databits)); + } + if (uart_configurations[i].parity_space) { + uart_configurations[i].parity_space &= !(usart_data_padded & (1 << uart_configurations[i].databits)); + } + // update parity count + uart_configurations[i].parity_possibilities = 0; + if (uart_configurations[i].parity_even) { + uart_configurations[i].parity_possibilities++; + } + if (uart_configurations[i].parity_odd) { + uart_configurations[i].parity_possibilities++; + } + if (uart_configurations[i].parity_mark) { + uart_configurations[i].parity_possibilities++; + } + if (uart_configurations[i].parity_space) { + uart_configurations[i].parity_possibilities++; + } + // verify word size + const uint16_t databits_mask = (0xffff << (uart_configurations[i].databits + ((0 == uart_configurations[i].parity_possibilities) ? 0 : 1))); // mask for bits which should not be cleared + if (~usart_data_padded & databits_mask) { // see if bit outside the word size are cleared + uart_configurations[i].databits_matching = false; + } + } + bool no_valid_configuration = true; + uint8_t new_valid_configuration = LENGTH(uart_configurations); + char parity = '?'; + for (uint8_t i = 0; i < LENGTH(uart_configurations); i++) { + // skip check the word size is wrong + if (!uart_configurations[i].databits_matching) { + continue; + } + no_valid_configuration = false; + if (uart_configurations[i].parity_possibilities > 1) { // parity is not yet clear + continue; + } else if (uart_configurations[i].parity_even) { + parity = 'E'; + } else if (uart_configurations[i].parity_odd) { + parity = 'O'; + } else if (uart_configurations[i].parity_mark) { + parity = 'M'; + } else if (uart_configurations[i].parity_space) { + parity = 'S'; + } else if (0==uart_configurations[i].parity_possibilities) { + parity = 'N'; + } + new_valid_configuration = i; + break; // stop searching since we found a configuration + } + if (no_valid_configuration) { + reset_state = true; // reset the configurations + pulse_duration = UINT32_MAX; // also reset the baud rate + uart_baudrate = 0; // also reset the baud rate + } else if (new_valid_configuration < LENGTH(uart_configurations) && '?' != parity && (new_valid_configuration != uart_configuration_valid || parity != uart_configuration_parity)) { // we found a new valid configuration + uart_configuration_valid = new_valid_configuration; + uart_configuration_parity = parity; + printf("\nnew UART configuration found: %u %u%c1\n", uart_baudrate, uart_configurations[uart_configuration_valid].databits, uart_configuration_parity); + } + // print received data if a configuration has been found + if (uart_configuration_valid < LENGTH(uart_configurations)) { // valid configuration existing + if (uart_configurations[uart_configuration_valid].databits >= 7 && usart_data_relevant < 0x80) { // this is probably valid ASCII data + putc(usart_data_relevant); + } else { + printf("0x%02x ", usart_data_relevant); + } + } else { + printf("0b%09b\n", usart_data_relevant); + } + } + } + user_input_get(); // clean input + + // clean up + gpio_set(GPIO_PORT(SHIFT_EN_PIN), GPIO_PIN(SHIFT_EN_PIN)); // remove power from level shifters pull-up + gpio_mode_setup(GPIO_PORT(UART_RX), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(UART_RX)); // put pin back to safe floating mode + gpio_mode_setup(GPIO_PORT(UART_TX), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(UART_TX)); // put pin back to safe floating mode + mux_select(-1); // disable multiplexer + timer_disable_counter(TIM(MONITOR_TIMER)); // disable timer + rcc_periph_reset_pulse(RST_TIM(MONITOR_TIMER)); // reset timer state + rcc_periph_clock_disable(RCC_TIM(MONITOR_TIMER)); // disable clock for timer peripheral + timer_disable_counter(TIM(FREQUENCY_TIMER)); // disable timer + nvic_disable_irq(NVIC_TIM_IRQ(FREQUENCY_TIMER)); // catch interrupts for this timer + timer_disable_irq(TIM(FREQUENCY_TIMER), TIM_DIER_UIE); // disable update interrupt for timer + timer_disable_irq(TIM(FREQUENCY_TIMER), TIM_DIER_CCIE(FREQUENCY_CHANNEL)); // disable capture interrupt + timer_ic_disable(TIM(FREQUENCY_TIMER), TIM_IC(FREQUENCY_CHANNEL)); // disable capture interrupt + rcc_periph_reset_pulse(RST_TIM(FREQUENCY_TIMER)); // reset timer state + rcc_periph_clock_disable(RCC_TIM(FREQUENCY_TIMER)); // disable clock for timer peripheral + rcc_periph_reset_pulse(RST_USART(UART_ID)); // reset USART peripheral + rcc_periph_clock_disable(RCC_USART(UART_ID)); // disable clock for USART peripheral +} + /** set first channel of range to scan * @param[in] argument optional pointer to first channel number */ @@ -889,9 +1196,17 @@ static const struct menu_command_t menu_commands[] = { .name = "monitor_single", .command_description = "monitor single channel activity", .argument = MENU_ARGUMENT_UNSIGNED, - .argument_description = "channel", + .argument_description = "CH", .command_handler = &command_monitor_single, }, + { + .shortcut = 'a', + .name = "uart_auto", + .command_description = "autodetect UART configuration", + .argument = MENU_ARGUMENT_UNSIGNED, + .argument_description = "CH", + .command_handler = &command_uart_autodetect, + }, { .shortcut = 'c', .name = "start",