application: add UART autodetection (copied from BusVoodoo)

This commit is contained in:
King Kévin 2021-05-08 10:34:15 +02:00
parent 1aa498d5bc
commit 666ae736ef
1 changed files with 317 additions and 2 deletions

View File

@ -27,6 +27,7 @@
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/adc.h> // ADC utilities
#include <libopencm3/stm32/timer.h> // timer library
#include <libopencm3/stm32/usart.h> // 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",