application: add UART autodetection (copied from BusVoodoo)
This commit is contained in:
parent
1aa498d5bc
commit
666ae736ef
319
application.c
319
application.c
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue