/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
/** BusVoodoo generic UART mode (code)
 *  @note this only contains the common UART methods and should be supplied with mode specific methods and information
 *  @file busvoodoo_uart_generic.c
 *  @author King Kévin <kingkevin@cuvoodoo.info>
 *  @date 2018
 */
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities

/* STM32 (including CM3) libraries */
#include <libopencm3/cm3/nvic.h> // interrupt utilities
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/usart.h> // USART utilities
#include <libopencm3/stm32/timer.h> // timer library

/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#include "interrupt.h" // user interrupt table
#include "menu.h" // menu definitions
#include "usart_enhanced.h" // utilities for USART enhancements
#include "busvoodoo_global.h" // BusVoodoo definitions
#include "busvoodoo_uart_generic.h" // own definitions

/** the USART mode specific information */
static const struct busvoodoo_uart_generic_specific_t* busvoodoo_uart_generic_specific = NULL;
/** mode setup stage */
static enum busvoodoo_uart_generic_setting_t {
	BUSVOODOO_UART_SETTING_NONE,
	BUSVOODOO_UART_SETTING_BAUDRATE,
	BUSVOODOO_UART_SETTING_DATABITS,
	BUSVOODOO_UART_SETTING_PARITY,
	BUSVOODOO_UART_SETTING_STOPBITS,
	BUSVOODOO_UART_SETTING_HWFLOWCTL,
	BUSVOODOO_UART_SETTING_DRIVE,
	BUSVOODOO_UART_SETTING_DONE,
} busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_NONE; /**< current mode setup stage */
/** UART baud rate (in bps) */
static uint32_t busvoodoo_uart_generic_baudrate = 115200;
/** UART data bits */
static uint8_t busvoodoo_uart_generic_databits = 8;
/** UART parity setting */
static enum usart_enhanced_parity_t busvoodoo_uart_generic_parity = USART_ENHANCED_PARITY_NONE;
/** UART stop bits setting */
static uint32_t busvoodoo_uart_generic_stopbits = USART_STOPBITS_1;
/** UART hardware flow control setting (true = with hardware flow control, false = without hardware flow control */
static bool busvoodoo_uart_generic_hwflowctl = false;
/** pin drive mode (true = push-pull, false = open-drain) */
static bool busvoodoo_uart_generic_drive = true;
/** if embedded pull-up resistors are used */
static bool busvoodoo_uart_generic_pullup = false;

/** set if the timer ISR should be set in the interrupt table instead of the vector table
 *  @note the vector table is faster, but doesn't allow to change the ISR
 *  @warning for the BusVoodoo UART (using USART3) and RS-232/RS-485 (using USART2) the RX is on TIM2_CH4 (remapped for USART3)
 */
#define BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE false

bool busvoodoo_uart_generic_configure(const struct busvoodoo_uart_generic_specific_t* conf)
{
	busvoodoo_uart_generic_specific = NULL; // reset specific information
	if (NULL==conf) {
		return false;
	}
	if (!conf->usart || !conf->usart_rcc || !conf->usart_rst) {
		return false;
	}
	if (!conf->tx_rcc || !conf->rx_rcc) {
		return false;
	}
	if (conf->hwflowctl && (!conf->rts_rcc || !conf->cts_rcc)) {
		return false;
	}
	if (conf->timer && (!conf->timer_rcc || !conf->timer_port_rcc || !(NVIC_TIM2_IRQ==conf->timer_nvic_irq || NVIC_TIM3_IRQ==conf->timer_nvic_irq || NVIC_TIM4_IRQ==conf->timer_nvic_irq || NVIC_TIM5_IRQ==conf->timer_nvic_irq))) {
		return false;
	}
	busvoodoo_uart_generic_specific = conf;
	return true;
}

bool busvoodoo_uart_generic_setup(char** prefix, const char* line)
{
	if (NULL==busvoodoo_uart_generic_specific) { // there is nothing to configure
		return true;
	}
	bool complete = false; // is the setup complete
	if (NULL==line) { // first call
		busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_NONE; // re-start configuration
	}
	switch (busvoodoo_uart_generic_setting) {
		case BUSVOODOO_UART_SETTING_NONE:
			snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "baud rate in bps (1200-2000000) [%u]", busvoodoo_uart_generic_baudrate);
			*prefix = busvoodoo_global_string; // ask for baud rate
			busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_BAUDRATE;
			break;
		case BUSVOODOO_UART_SETTING_BAUDRATE:
			if (NULL==line || 0==strlen(line)) { // use default setting
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DATABITS; // go to next setting
			} else { // setting provided
				uint32_t baudrate = atoi(line); // parse setting
				if (baudrate>0 && baudrate<=2000000) { // check setting
					busvoodoo_uart_generic_baudrate = baudrate; // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DATABITS; // go to next setting
				}
			}
			if (BUSVOODOO_UART_SETTING_DATABITS==busvoodoo_uart_generic_setting) { // if next setting
				snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "data bits (5-8) [%u]", busvoodoo_uart_generic_databits); // prepare next setting
				*prefix = busvoodoo_global_string; // display next setting
			}
			break;
		case BUSVOODOO_UART_SETTING_DATABITS:
			if (NULL==line || 0==strlen(line)) { // use default setting
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_PARITY; // go to next setting
			} else if (1==strlen(line)) { // setting provided
				uint8_t databits = atoi(line); // parse setting
				if (databits>=5 && databits<=8) { // check setting
					busvoodoo_uart_generic_databits = databits; // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_PARITY; // go to next setting
				}
			}
			if (BUSVOODOO_UART_SETTING_PARITY==busvoodoo_uart_generic_setting) { // if next setting
				printf("1) none\n");
				printf("2) even\n");
				printf("3) odd\n");
				printf("4) mark\n");
				printf("5) space\n");
				snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "parity (1,2,3,4,5) [%u]", busvoodoo_uart_generic_parity+1); // prepare next setting
				*prefix = busvoodoo_global_string; // display next setting
			}
			break;
		case BUSVOODOO_UART_SETTING_PARITY:
			if (NULL==line || 0==strlen(line)) { // use default setting
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_STOPBITS; // go to next setting
			} else if (1==strlen(line)) { // setting provided
				uint8_t parity = atoi(line); // parse setting
				if (parity>0 && parity<6) { // check setting
					busvoodoo_uart_generic_parity = parity-1;
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_STOPBITS; // go to next setting
				}
			}
			if (BUSVOODOO_UART_SETTING_STOPBITS==busvoodoo_uart_generic_setting) { // if next setting
				printf("1) 0.5\n");
				printf("2) 1\n");
				printf("3) 1.5\n");
				printf("4) 2\n");
				snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "stop bits (1,2,3,4) [%c]", USART_STOPBITS_0_5==busvoodoo_uart_generic_stopbits ? '1' : (USART_STOPBITS_1==busvoodoo_uart_generic_stopbits ? '2' : (USART_STOPBITS_1_5==busvoodoo_uart_generic_stopbits ? '3' : '4'))); // prepare next setting
				*prefix = busvoodoo_global_string; // display next setting
			}
			break;
		case BUSVOODOO_UART_SETTING_STOPBITS:
			if (NULL==line || 0==strlen(line)) { // use default setting
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_HWFLOWCTL; // go to next setting
			} else if (1==strlen(line)) { // setting provided
				if ('1'==line[0]) { // 0.5 stop bits
					busvoodoo_uart_generic_stopbits = USART_STOPBITS_0_5; // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_HWFLOWCTL; // go to next setting
				} else if ('2'==line[0]) { // 1 stop bits
					busvoodoo_uart_generic_stopbits = USART_STOPBITS_1; // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_HWFLOWCTL; // go to next setting
				} else if ('3'==line[0]) { // 1.5 stop bits
					busvoodoo_uart_generic_stopbits = USART_STOPBITS_1_5; // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_HWFLOWCTL; // go to next setting
				} else if ('4'==line[0]) { // 2 stop bits
					busvoodoo_uart_generic_stopbits = USART_STOPBITS_2; // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_HWFLOWCTL; // go to next setting
				}
			}
			if (BUSVOODOO_UART_SETTING_HWFLOWCTL==busvoodoo_uart_generic_setting) { // if next setting
				if (!busvoodoo_uart_generic_specific->hwflowctl) { // hardware flow control is not supported
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DRIVE; // go to next setting
					goto setting_drive; // actually go to next setting
				}
				printf("1) no flow control\n");
				printf("2) RTS/CTS hardware flow control\n");
				snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "flow control (1,2) [%c]", busvoodoo_uart_generic_hwflowctl ? '2' : '1'); // prepare next setting
				*prefix = busvoodoo_global_string; // display next setting
			}
			break;
		case BUSVOODOO_UART_SETTING_HWFLOWCTL:
			if (NULL==line || 0==strlen(line)) { // use default setting
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DRIVE; // go to next setting
			} else if (1==strlen(line)) { // setting provided
				if ('1'==line[0] || '2'==line[0]) { // setting provided
					busvoodoo_uart_generic_hwflowctl = ('2'==line[0]); // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DRIVE; // go to next setting
				}
			}
setting_drive:
			if (BUSVOODOO_UART_SETTING_DRIVE==busvoodoo_uart_generic_setting) { // if next setting
				if (!busvoodoo_uart_generic_specific->multidrive) {
					busvoodoo_uart_generic_drive = true; // only push-pull driving mode is supported
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DONE; // go to next setting
					goto setting_done; // actually go to next setting
				}
				printf("1) push-pull (3.3V)\n");
				printf("2) open-drain, with embedded pull-up resistors (2kO)\n");
				printf("3) open-drain, with external pull-up resistors\n");
				snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "drive mode (1,2,3) [%c]", busvoodoo_uart_generic_drive ? '1' : (busvoodoo_uart_generic_pullup ? '2' : '3')); // show drive mode
				*prefix = busvoodoo_global_string; // display next setting
			}
			break;
		case BUSVOODOO_UART_SETTING_DRIVE:
			if (NULL==line || 0==strlen(line)) { // use default setting
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DONE; // go to next setting
			} else if (1==strlen(line)) { // setting provided
				uint8_t drive = atoi(line); // parse setting
				if (1==drive || 2==drive || 3==drive) { // check setting
					busvoodoo_uart_generic_drive = (1==drive); // remember setting
					busvoodoo_uart_generic_pullup = (2==drive); // remember setting
					busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_DONE; // go to next setting
				}
			}
setting_done:
			if (BUSVOODOO_UART_SETTING_DONE==busvoodoo_uart_generic_setting) { // we have all settings, configure UART
				rcc_periph_clock_enable(RCC_AFIO); // enable clock for USART alternate function
				rcc_periph_clock_enable(busvoodoo_uart_generic_specific->usart_rcc); // enable clock for USART peripheral
				rcc_periph_reset_pulse(busvoodoo_uart_generic_specific->usart_rst); // reset USART peripheral
				usart_set_baudrate(busvoodoo_uart_generic_specific->usart, busvoodoo_uart_generic_baudrate); // set baud rate
				usart_enhanced_config(busvoodoo_uart_generic_specific->usart, busvoodoo_uart_generic_databits, busvoodoo_uart_generic_parity); // use enhanced USART to configure the USART peripherals, supporting more data-bits and parity configurations
				usart_set_stopbits(busvoodoo_uart_generic_specific->usart, busvoodoo_uart_generic_stopbits); // set stop bits
				if (busvoodoo_uart_generic_specific->hwflowctl && busvoodoo_uart_generic_hwflowctl) {
					usart_set_flow_control(busvoodoo_uart_generic_specific->usart, USART_FLOWCONTROL_RTS_CTS); // set RTS/CTS flow control
				} else {
					usart_set_flow_control(busvoodoo_uart_generic_specific->usart, USART_FLOWCONTROL_NONE); // set no flow control
				}
				usart_set_mode(busvoodoo_uart_generic_specific->usart, USART_MODE_TX_RX); // full-duplex communication
				rcc_periph_clock_enable(busvoodoo_uart_generic_specific->tx_rcc); // enable clock for USART GPIO peripheral
				rcc_periph_clock_enable(busvoodoo_uart_generic_specific->rx_rcc); // enable clock for USART GPIO peripheral
				if (busvoodoo_uart_generic_specific->hwflowctl && busvoodoo_uart_generic_hwflowctl) {
					rcc_periph_clock_enable(busvoodoo_uart_generic_specific->rts_rcc); // enable clock for USART GPIO peripheral
					rcc_periph_clock_enable(busvoodoo_uart_generic_specific->cts_rcc); // enable clock for USART GPIO peripheral
				}
				if (busvoodoo_uart_generic_drive) { // use push-pull drive mode
					gpio_set_mode(busvoodoo_uart_generic_specific->tx_port, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, busvoodoo_uart_generic_specific->tx_pin); // setup GPIO pin USART transmit
					gpio_set(busvoodoo_uart_generic_specific->rx_port, busvoodoo_uart_generic_specific->rx_pin); // pull up to avoid noise when not connected
					gpio_set_mode(busvoodoo_uart_generic_specific->rx_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, busvoodoo_uart_generic_specific->rx_pin); // setup GPIO pin USART receive
					if (busvoodoo_uart_generic_specific->hwflowctl && busvoodoo_uart_generic_hwflowctl) { // use open drain drive mode
						gpio_set_mode(busvoodoo_uart_generic_specific->rts_port, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, busvoodoo_uart_generic_specific->rts_pin); // setup GPIO pin USART transmit
						gpio_set(busvoodoo_uart_generic_specific->cts_port, busvoodoo_uart_generic_specific->cts_pin); // pull up to block transmission unless requested
						gpio_set_mode(busvoodoo_uart_generic_specific->cts_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, busvoodoo_uart_generic_specific->cts_pin); // setup GPIO pin USART receive
					}
				} else {
					gpio_set_mode(busvoodoo_uart_generic_specific->tx_port, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, busvoodoo_uart_generic_specific->tx_pin); // setup GPIO pin USART transmit
					gpio_set_mode(busvoodoo_uart_generic_specific->rx_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->rx_pin); // setup GPIO pin USART receive
					if (busvoodoo_uart_generic_specific->hwflowctl && busvoodoo_uart_generic_hwflowctl) {
						gpio_set_mode(busvoodoo_uart_generic_specific->rts_port, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, busvoodoo_uart_generic_specific->rts_pin); // setup GPIO pin USART transmit
						gpio_set_mode(busvoodoo_uart_generic_specific->cts_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->cts_pin); // setup GPIO pin USART receive
					}
				}
				if (!busvoodoo_uart_generic_drive && busvoodoo_uart_generic_pullup) { // enable embedded pull-ups if used
					busvoodoo_embedded_pullup(true); // set embedded pull-ups
					printf("use LV to set pull-up voltage\n");
				}
				usart_enable(busvoodoo_uart_generic_specific->usart); // enable USART
				// setup timer to measure RX edge timing for baud rate guessing
				if (busvoodoo_uart_generic_specific->timer) {
				}
				busvoodoo_led_blue_off(); // disable blue LED because there is no activity
				busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_NONE; // restart settings next time
				complete = true; // configuration is complete
			}
			break;
		default: // unknown case
			busvoodoo_uart_generic_setting = BUSVOODOO_UART_SETTING_NONE; // restart settings next time
			break;
	}
	return complete;
}

void busvoodoo_uart_generic_exit(void)
{
	if (NULL==busvoodoo_uart_generic_specific) {
		return;
	}
	usart_disable(busvoodoo_uart_generic_specific->usart); // disable USART
	rcc_periph_clock_disable(busvoodoo_uart_generic_specific->usart_rcc); // disable domain clock
	gpio_set_mode(busvoodoo_uart_generic_specific->tx_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->tx_pin); // set pin back to floating input
	gpio_set_mode(busvoodoo_uart_generic_specific->rx_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->rx_pin); // set pin back to floating input
	if (busvoodoo_uart_generic_specific->hwflowctl) {
		gpio_set_mode(busvoodoo_uart_generic_specific->rts_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->rts_pin); // set pin back to floating input
		gpio_set_mode(busvoodoo_uart_generic_specific->cts_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->cts_pin); // set pin back to floating input
	}
	if (busvoodoo_uart_generic_specific->multidrive) {
		busvoodoo_embedded_pullup(false); // disable embedded pull-ups
	}
	busvoodoo_uart_generic_specific = NULL; // remove specific information
}

/** write to UART
 *  @param[in] value value to write
 */
static void busvoodoo_uart_generic_write(uint8_t value)
{
	if (NULL==busvoodoo_uart_generic_specific) {
		return;
	}
	if (busvoodoo_uart_generic_specific->tx_pre) {
		(*busvoodoo_uart_generic_specific->tx_pre)();
	}
	while ((0==((USART_SR(busvoodoo_uart_generic_specific->usart)) & USART_SR_TXE) && !user_input_available)); // wait for transmit buffer to be empty (or user to interrupt)
	if ((USART_SR(busvoodoo_uart_generic_specific->usart)) & USART_SR_TXE) { // we can send data
		// send data
		busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // pulse blue LED to show transmission
		usart_enhanced_send(busvoodoo_uart_generic_specific->usart, value); // transmit data
		// display data send
		printf("write: '%c'/0x%02x\n", value, value);
	}
	while ((0==((USART_SR(busvoodoo_uart_generic_specific->usart)) & USART_SR_TC) && !user_input_available)); // wait for transfer to be complete
	if (user_input_available) { // user interrupted flow
		user_input_get(); // discard user input
	}
	if (busvoodoo_uart_generic_specific->tx_post) {
		(*busvoodoo_uart_generic_specific->tx_post)();
	}
}

/** read from UART
 */
static void busvoodoo_uart_generic_read(void)
{
	if (NULL==busvoodoo_uart_generic_specific) {
		return;
	}
	if (busvoodoo_uart_generic_specific->rx_pre) {
		(*busvoodoo_uart_generic_specific->rx_pre)();
	}
	printf("read: ");
	while (!(USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_RXNE) && !user_input_available); // wait for incoming data to be available (or user input to exit)
	if ((USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_RXNE)) { // verify if data has been received
		busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // enable blue LED to show reception
		// get the errors
		bool error_noise = (0!=(USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_NE)); // read noise error flag
		bool error_framing = (0!=(USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_FE)); // read frame error flag
		uint8_t input = usart_enhanced_recv(busvoodoo_uart_generic_specific->usart); // read received character (also clears the error flags)
		// display data
		printf("'%c'/0x%02x", input, input);
		// display errors
		printf("(");
		if (error_noise) {
			printf("noise");
		} else if (error_framing) {
			printf("framing");
		} else if (usart_enhanced_parity_error(busvoodoo_uart_generic_specific->usart)) {
			printf("parity");
		} else {
			printf("no");
		}
		printf(" error)");
	}
	printf("\n");
	if (user_input_available) { // user interrupted flow
		user_input_get(); // discard user input
	}
	if (busvoodoo_uart_generic_specific->rx_post) {
		(*busvoodoo_uart_generic_specific->rx_post)();
	}
}

/** perform UART action
 *  @param[in] action action to perform
 *  @param[in] repetition how many times to perform the action
 *  @param[in] perform the action (true) or just check it (false)
 *  @return true if the action has been performed, false if it is malformed
 */
static bool busvoodoo_uart_generic_action(const char* action, uint32_t repetition, bool perform)
{
	uint32_t length = strlen(action); // remember length since it will be used a number of times
	if (NULL==action || 0==length) { // there is nothing to do
		return true;
	}

	if (1==length && 'r'==action[0]) { // read data
		if (!perform) {
			return true;
		}
		for (uint32_t i=0; i<repetition; i++) {
			busvoodoo_uart_generic_read(); // read from UART
		}
	} else if (1==length && 'u'==action[0]) { // sleep us
		if (!perform) {
			return true;
		}
		printf("wait for %u us\n", repetition);
		sleep_us(repetition); // sleep
	} else if (1==length && 'm'==action[0]) { // sleep ms
		if (!perform) {
			return true;
		}
		printf("wait for %u ms\n", repetition);
		sleep_ms(repetition); // sleep
	} else if ('0'==action[0]) { // send digit
		if (1==length) { // just send 0
			if (!perform) {
				return true;
			}
			for (uint32_t i=0; i<repetition; i++) {
				busvoodoo_uart_generic_write(0); // write to UART
			}
		} else if ('x'==action[1] || 'b'==action[1]) { // send hex/binary
			return busvoodoo_uart_generic_action(action+1, repetition, perform); // just retry without leading 0
		} else if (action[1]>='0' && action[1]<='9') { // send decimal
			return busvoodoo_uart_generic_action(action+1, repetition, perform); // just retry without leading 0
		} else { // malformed action
			return false;
		}
	} else if ('x'==action[0] && length>1) { // send hexadecimal value
		for (uint32_t i=1; i<length; i++) { // check string
			if (!((action[i]>='0' && action[i]<='9') || (action[i]>='a' && action[i]<='f') || (action[i]>='A' && action[i]<='F'))) { // check for hexadecimal character
				return false; // not an hexadecimal string
			}
		}
		if (!perform) {
			return true;
		}
		uint32_t value = strtol(&action[1], NULL, 16); // get hex value
		for (uint32_t i=0; i<repetition; i++) {
			busvoodoo_uart_generic_write(value); // write to SPI
		}
	} else if ('b'==action[0] && length>1) { // send binary value
		for (uint32_t i=1; i<length; i++) { // check string
			if (action[i]<'0' || action[i]>'1') { // check for binary character
				return false; // not a binary string
			}
		}
		if (!perform) {
			return true;
		}
		uint32_t value = strtol(&action[1], NULL, 2); // get binary value
		for (uint32_t i=0; i<repetition; i++) {
			busvoodoo_uart_generic_write(value); // write to SPI
		}
	} else if (action[0]>='1' && action[0]<='9') { // send decimal value
		for (uint32_t i=1; i<length; i++) { // check string
			if (action[i]<'0' || action[i]>'9') { // check for decimal character
				return false; // not a decimal string
			}
		}
		if (!perform) {
			return true;
		}
		uint32_t value = strtol(&action[0], NULL, 10); // get decimal value
		for (uint32_t i=0; i<repetition; i++) {
			busvoodoo_uart_generic_write(value); // write to SPI
		}
	} else if (length>=2 && ('"'==action[0] || '\''==action[0]) && (action[length-1]==action[0])) { // send ASCII character
		if (!perform) {
			return true;
		}
		for (uint32_t r=0; r<repetition; r++) {
			for (uint32_t i=1; i<length-1; i++) { // go through string
				busvoodoo_uart_generic_write(action[i]); // write to SPI
			}
		}
	} else { // malformed action
		return false;
	}
	return true; // all went well
}

// command handlers

/** command to perform actions
 *  @param[in] argument actions to perform
 */
static void busvoodoo_uart_generic_command_actions(void* argument)
{
	if (NULL==argument || 0==strlen(argument)) {
		printf("available actions (separated by space or ,):\n");
		printf("0\twrite decimal value\n");
		printf("0x0\twrite hexadecimal value\n");
		printf("0b0\twrite binary value\n");
		printf("\"a\"/'a'\twrite ASCII characters\n");
		printf("r\tread value\n");
		printf("u/m\twait 1 us/ms\n");
		printf(":n\trepeat action n times\n");
		return;
	}

	// copy argument since it will be modified
	char* copy = calloc(strlen(argument)+1, sizeof(char));
	if (!copy) {
		while (true);
	}
	strncpy(copy, argument, strlen(argument)+1);
	// verify and perform actions
	if (!busvoodoo_global_actions(copy, false, &busvoodoo_uart_generic_action)) { // verify actions
		printf("malformed action(s)\n");
	} else { // action are ok
		printf("press any key to exit\n");
		busvoodoo_global_actions(argument, true, &busvoodoo_uart_generic_action); // perform action
		if (user_input_available) { // user interrupted flow
			user_input_get(); // discard user input
		}
	}
	free(copy); // release memory
}

/** command to transmit a string
 *  @param[in] argument string to transmit (CR+LF when none provided)
 */
static void busvoodoo_uart_generic_command_transmit(void* argument)
{
	if (NULL==busvoodoo_uart_generic_specific) {
		return;
	}
	if (busvoodoo_uart_generic_specific->tx_pre) {
		(*busvoodoo_uart_generic_specific->tx_pre)();
	}
	if (NULL==argument || 0==strlen(argument)) { // nothing to transmit
		argument = "\r\n"; // transmit CR+LF
	}
	printf("press any key to exit\n");
	for (uint16_t i=0; ((char*)(argument))[i] && !user_input_available; i++) {
		while ((0==(USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_TXE) && !user_input_available)); // wait for transmit buffer to be empty
		if (USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_TXE) { // we can send a character
			printf("%c", ((char*)(argument))[i]); // echo character to transmit
			busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // pulse blue LED to show transmission
			usart_enhanced_send(busvoodoo_uart_generic_specific->usart, ((char*)(argument))[i]); // transmit character
		}
	}
	while ((0==((USART_SR(busvoodoo_uart_generic_specific->usart)) & USART_SR_TC) && !user_input_available)); // wait for transfer to be complete
	if (user_input_available) { // user interrupted flow
		user_input_get(); // discard user input
	}
	if (strcmp(argument, "\r\n")) {
		printf("\n");
	}
	if (busvoodoo_uart_generic_specific->tx_post) {
		(*busvoodoo_uart_generic_specific->tx_post)();
	}
}

/** command to receive data
 *  @param[in] argument in which format to display
 */
static void busvoodoo_uart_generic_command_receive(void* argument)
{
	bool display_hex = false; // display in hex
	bool display_bin = false; // display in bin
	if (NULL!=argument && strlen(argument)>0) {
		if (0==strcmp(argument, "h") || 0==strcmp(argument, "hex")) { // user wants hexadecimal display
			display_hex = true; // remember to display in hexadecimal
		} else if (0==strcmp(argument, "b") || 0==strcmp(argument, "bin")) { // user wants binary display
			display_bin = true; // remember to display in binary
		} else {
			printf("malformed argument\n");
			return;
		}
	}
	if (NULL==busvoodoo_uart_generic_specific) {
		return;
	}
	if (busvoodoo_uart_generic_specific->rx_pre) {
		(*busvoodoo_uart_generic_specific->rx_pre)();
	}
	printf("press any key to exit\n");
	while (!user_input_available) { // check for user input to exit
		if ((USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_RXNE)) { // verify if data has been received
			uint8_t input = usart_enhanced_recv(busvoodoo_uart_generic_specific->usart); // receive character
			busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // enable blue LED to show reception
			if (display_hex) { // display data in hex
				printf("%02x ", input);
			} else if (display_bin) { // display data in binary
				printf("%08b ", input);
			} else { // display in ASCII
				printf("%c", input); // print received character
			}
		}
	}
	user_input_get(); // discard user input
	printf("\n"); // get to next line
	if (busvoodoo_uart_generic_specific->rx_post) {
		(*busvoodoo_uart_generic_specific->rx_post)();
	}
}

/** command to transmit and receive data
 *  @param[in] argument no argument required
 */
static void busvoodoo_uart_generic_command_transceive(void* argument)
{
	(void)argument; // we won't use the argument
	if (NULL==busvoodoo_uart_generic_specific) {
		return;
	}
	if (busvoodoo_uart_generic_specific->rx_pre) {
		(*busvoodoo_uart_generic_specific->rx_pre)();
	}
	printf("press 5 times escape to exit\n");
	char last_c = 0; // last user character received
	uint8_t esc_count = 0; // number of times escape has press received
	while (true) { // check for escape sequence
		if (user_input_available) { // check if user wants to transmit something
			char c = user_input_get(); // get user input
			if (0x1b==c) { // user pressed escape
				if (0x1b!=last_c) { // this is the first escape press
					esc_count = 0;
				}
				esc_count++; // increment escape count
			}
			last_c = c; // remember key press
			if (esc_count<5) { // check for escape sequence
				if (busvoodoo_uart_generic_specific->rx_post) {
					(*busvoodoo_uart_generic_specific->rx_post)();
				}
				if (busvoodoo_uart_generic_specific->tx_pre) {
					(*busvoodoo_uart_generic_specific->tx_pre)();
				}
				while ((0==(USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_TXE) && !user_input_available)); // wait for transmit buffer to be empty
				if (USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_TXE) { // we can send a character
					usart_enhanced_send(busvoodoo_uart_generic_specific->usart, c); // send user character
					busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // enable blue LED to show transmission
				}
				while ((0==((USART_SR(busvoodoo_uart_generic_specific->usart)) & USART_SR_TC) && !user_input_available)); // wait for transfer to be complete
				if (busvoodoo_uart_generic_specific->tx_post) {
					(*busvoodoo_uart_generic_specific->tx_post)();
				}
				if (busvoodoo_uart_generic_specific->rx_pre) {
					(*busvoodoo_uart_generic_specific->rx_pre)();
				}
			} else { // user wants to exit
				break; // exit infinite loop
			}
		}
		if ((USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_RXNE)) { // verify if data has been received
			char input = usart_enhanced_recv(busvoodoo_uart_generic_specific->usart); // receive character
			busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // enable blue LED to show reception
			printf("%c", input); // print received character
		}
	}
	printf("\n"); // get to next line
	if (busvoodoo_uart_generic_specific->rx_post) {
		(*busvoodoo_uart_generic_specific->rx_post)();
	}
}

/** command to verify incoming transmission for error
 *  @param[in] argument argument not required
 */
static void busvoodoo_uart_generic_command_error(void* argument)
{
	(void)argument; // argument not used
	printf("press any key to exit\n");
	while (!user_input_available) { // wait until user interrupt
		busvoodoo_uart_generic_read(); // read incoming data (this also checks for errors
	}
	user_input_get(); // discard user input
}

/** 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;
}

volatile bool pulse_flag = false; /**< set when a small pulse time is detected */
volatile uint32_t pulse_duration = UINT32_MAX; /**< smallest pulse duration measured */

/** timer ISR to measure edge timing */
#if defined(BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE) && BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE
static void timer_isr(void)
#else
void TIM_ISR(2)(void)
#endif
{
	busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE);
	static uint32_t pulse = UINT32_MAX; // measured pulse duration (MAX is an invalid values)
	if (timer_get_flag(busvoodoo_uart_generic_specific->timer, TIM_SR_UIF)) { // overflow update event happened
		timer_clear_flag(busvoodoo_uart_generic_specific->timer, TIM_SR_UIF); // clear flag
		if (pulse>(UINT32_MAX-0x10000)) { // we can't measure longer pulser (and baud rate < 0.017 bps make no sense)
			pulse = UINT32_MAX; // invalidate measured pulse
		} else {
			pulse += 0x10000; // account for the 16-bit timer limit
		}
	}
	if (timer_get_flag(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_sr_ccof)) { // capture overflow occurred
		timer_clear_flag(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_sr_ccof); // clear flag
		pulse = UINT32_MAX; // invalidate measured pulse
	}
	if (timer_get_flag(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_sr_ccif)) {
		uint16_t edge = *busvoodoo_uart_generic_specific->timer_ccr; // retrieve captured value (clears flag)
		if (UINT32_MAX!=pulse) { // only calculate pulse if previous edge is valid
			pulse = ((pulse&0xffff0000)+edge)-(pulse&0xffff); // calculate pulse duration
			if (pulse<pulse_duration) { // save new pulse duration if smaller
				pulse_duration = pulse;
				pulse_flag = true;
			}
		}
		pulse = edge; // replace with current edge time
	}
}

/** command to auto-detect incoming UART configuration (baud rate, data bits, parity)
 *  @param[in] argument argument not required
 *  @remark uses timer input capture for the baud rate, and guess checking for the data bits and parity
 */
static void busvoodoo_uart_generic_command_detect(void* argument)
{
	(void)argument; // argument not used
	if (!busvoodoo_uart_generic_specific->timer) {
		printf("baud rate detection not possible (no timer available)\n");
		return;
	}
	printf("the more traffic is incoming, the better the detection\n");
	printf("press any key to exit\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)
	usart_set_baudrate(busvoodoo_uart_generic_specific->usart, 1200); // configure UART to pre-selected baud rate
	usart_set_databits(busvoodoo_uart_generic_specific->usart, uart_databits); // configure UART to pre-selected data-bits
	usart_set_stopbits(busvoodoo_uart_generic_specific->usart, USART_STOPBITS_1); // 1 stop-bits also complies to 2 stop-bits
	usart_set_parity(busvoodoo_uart_generic_specific->usart, USART_PARITY_NONE); // get the raw data since we will do the parity check ourselves

	// setup timer to generate/measure signal timing
	if ((busvoodoo_uart_generic_specific->timer_port != busvoodoo_uart_generic_specific->rx_port) || (busvoodoo_uart_generic_specific->timer_pin != busvoodoo_uart_generic_specific->rx_pin)) {
		rcc_periph_clock_enable(busvoodoo_uart_generic_specific->timer_port_rcc); // enable clock for GPIO peripheral
		gpio_set_mode(busvoodoo_uart_generic_specific->timer_port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, busvoodoo_uart_generic_specific->timer_pin); // switch pin to input to measure the target transmit baud rate
	}
	rcc_periph_clock_enable(busvoodoo_uart_generic_specific->timer_rcc); // enable clock for timer peripheral
	timer_reset(busvoodoo_uart_generic_specific->timer); // reset timer state
	timer_disable_counter(busvoodoo_uart_generic_specific->timer); // disable timer to configure it
	timer_set_mode(busvoodoo_uart_generic_specific->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(busvoodoo_uart_generic_specific->timer, 1-1); // don't use prescale so to get the most precise measurement
	timer_ic_set_input(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_ic, busvoodoo_uart_generic_specific->timer_ic_in_ti); // configure the input capture ICx to use the right channel TIn
	timer_ic_set_filter(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_ic, TIM_IC_OFF); // use no filter input to keep precise timing
	/* ideally we would trigger on any edge, allowing to measure the bit width (on 010 or 101 bit pattern) and calculate the correct baud rate.
	 * sadly the STM32 F1 family timer peripheral does not supporting triggering on both edges at the same time (the F0 family can).
	 * it is possible to simply update the polarity setting to capture the other edge, but this update seems to slow even in a ISR to capture > 1 MHz frequencies (=500 bps).
	 * thus we will start the trigger on the start bit, and wait for the next falling edge.
	 * this way we don't need to change the parity and have twice the time to measure the baud rate.
	 * to calculate the baud rate we just have to divided the frequency by two.
	 * the correct baud rate on the following pattern (HLHL), and since the start bit is the first HL pattern we just have to wait for data starting with 10 (LSb first)
	 */
	timer_ic_set_polarity(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_ic, TIM_IC_FALLING); // capture on falling end to trigger on the start bit
	timer_ic_set_prescaler(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_ic, TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
	timer_ic_enable(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_ic);  // enable capture interrupt
	timer_clear_flag(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_sr_ccif); // clear input compare flag
	timer_enable_irq(busvoodoo_uart_generic_specific->timer, busvoodoo_uart_generic_specific->timer_dier_ccie); // enable capture interrupt
	timer_update_on_overflow(busvoodoo_uart_generic_specific->timer); // only use counter overflow as UEV source (use overflow to measure longer times)
	timer_clear_flag(busvoodoo_uart_generic_specific->timer, TIM_SR_UIF); // clear overflow flag
	timer_enable_irq(busvoodoo_uart_generic_specific->timer, TIM_DIER_UIE); // enable update interrupt for timer
#if defined(BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE) && BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE
	/** backup of timer ISR before replacing it */
	void (*isr_backup)(void) = interrupt_table[busvoodoo_uart_generic_specific->timer_nvic_irq]; // backup current timer ISR before replacing it
	interrupt_table[busvoodoo_uart_generic_specific->timer_nvic_irq] = &timer_isr; // replace current timer ISR with our to be able to measure the edge timing
	bool irq_backup = nvic_get_irq_enabled(busvoodoo_uart_generic_specific->timer_nvic_irq); // backup enable IRQ setting
#endif
	nvic_enable_irq(busvoodoo_uart_generic_specific->timer_nvic_irq); // catch interrupts for this timer
	pulse_duration = UINT32_MAX; // reset pulse duration
	timer_enable_counter(busvoodoo_uart_generic_specific->timer); // enable timer

	// switch on RX
	if (busvoodoo_uart_generic_specific->rx_pre) {
		(*busvoodoo_uart_generic_specific->rx_pre)();
	}

	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 }; // list of standard baud rates, to match with measured frequency
	uint32_t uart_baudrate = 0;  // fastest found baud rate
	while (!user_input_available) {
		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(busvoodoo_uart_generic_specific->usart); // clear input buffer and allow flag to be set
			usart_enable(busvoodoo_uart_generic_specific->usart); // ensure UART is enabled
			reset_state = false;
		}
		if (pulse_flag) { // new pulse duration has been measured
			pulse_flag = false; // clear flag
			printf("u");
			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(busvoodoo_uart_generic_specific->usart); // disable UART before reconfiguring
					usart_set_baudrate(busvoodoo_uart_generic_specific->usart, 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(busvoodoo_uart_generic_specific->usart) & (USART_SR_NE|USART_SR_FE)) { // error on UART received
			usart_recv(busvoodoo_uart_generic_specific->usart); // 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 and idle frame)
				// two high probable 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(busvoodoo_uart_generic_specific->usart); // disable UART before reconfiguring
				usart_set_databits(busvoodoo_uart_generic_specific->usart, 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 too detected error\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.
			 * from the "STM32F10xxC/D/E silicon limitations" errata, section 2.12.2 "Idle frame is not detected if receiver clock speed is deviated": If the USART receives an idle frame followed by a character, and the clock of the transmitter device is faster than the USART receiver clock, the USART receive signal falls too early when receiving the character start bit, with the result that the idle frame is not detected (IDLE flag is not set).
			 * there this no workaround be it will be fixed when changing the baud rate
			 */
			if (USART_SR(busvoodoo_uart_generic_specific->usart) | USART_SR_IDLE) {
				wait_for_idle = false;
			}
			if (USART_SR(busvoodoo_uart_generic_specific->usart) | USART_SR_RXNE) {
				USART_DR(busvoodoo_uart_generic_specific->usart); // empty receive buffer so the IDLE flag can retrigger
			}
		}
		if (USART_SR(busvoodoo_uart_generic_specific->usart) & USART_SR_RXNE) { // data received
			uint16_t usart_data = usart_recv(busvoodoo_uart_generic_specific->usart); // save received data (also clears flag)
			if (0 == uart_baudrate) { // we did not find any valid baud rate yet
				continue;
			}
			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
			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
				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
					printf("%c", usart_data_relevant);
				} else {
					printf("0x%02x ", usart_data_relevant);
				}
			} else {
				printf("0b%09b\n", usart_data_relevant);
			}
		}
	}
	user_input_get(); // clear input used to interrupt previous loop
	printf("\n");

	// switch off RX
	if (busvoodoo_uart_generic_specific->rx_post) {
		(*busvoodoo_uart_generic_specific->rx_post)();
	}

	// stop timer
	timer_disable_counter(busvoodoo_uart_generic_specific->timer); // disable timer
#if defined(BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE) && BUSVOODOO_UART_TIMER_USE_INTERRUPT_TABLE
	interrupt_table[busvoodoo_uart_generic_specific->timer_nvic_irq] = isr_backup; // restore ISR
	if (!irq_backup) {
		nvic_disable_irq(busvoodoo_uart_generic_specific->timer_nvic_irq); // disable IRQ
	}
#else
	nvic_disable_irq(busvoodoo_uart_generic_specific->timer_nvic_irq); // disable IRQ
#endif
	timer_reset(busvoodoo_uart_generic_specific->timer); // reset timer
	rcc_periph_clock_disable(busvoodoo_uart_generic_specific->timer_rcc); // disable clock for timer peripheral
	// no need to disable pin since it's already a floating input it is not Rx

	if (uart_configuration_valid < LENGTH(uart_configurations)) {
		printf("press y to use configuration found: %u %u%c1\n", uart_baudrate, uart_configurations[uart_configuration_valid].databits, uart_configuration_parity);
		while (!user_input_available); // wait until user input
		if ('y' == user_input_get()) { // user want to use found configuration
			busvoodoo_uart_generic_baudrate = uart_baudrate;
			busvoodoo_uart_generic_databits = uart_configurations[uart_configuration_valid].databits;
			if (uart_configurations[uart_configuration_valid].parity_even) {
				busvoodoo_uart_generic_parity = USART_ENHANCED_PARITY_EVEN;
			} else if (uart_configurations[uart_configuration_valid].parity_odd) {
				busvoodoo_uart_generic_parity = USART_ENHANCED_PARITY_ODD;
			} else if (uart_configurations[uart_configuration_valid].parity_mark) {
				busvoodoo_uart_generic_parity = USART_ENHANCED_PARITY_MARK;
			} else  if (uart_configurations[uart_configuration_valid].parity_space) {
				busvoodoo_uart_generic_parity = USART_ENHANCED_PARITY_SPACE;
			} else {
				busvoodoo_uart_generic_parity = USART_ENHANCED_PARITY_NONE;
			}
			busvoodoo_uart_generic_stopbits = USART_STOPBITS_1;
		}
	}

	// reconfigure USART
	usart_set_baudrate(busvoodoo_uart_generic_specific->usart, busvoodoo_uart_generic_baudrate); // set baud rate
	usart_enhanced_config(busvoodoo_uart_generic_specific->usart, busvoodoo_uart_generic_databits, busvoodoo_uart_generic_parity); // use enhanced USART to configure the USART peripherals, supporting more data-bits and parity configurations
	usart_set_stopbits(busvoodoo_uart_generic_specific->usart, busvoodoo_uart_generic_stopbits); // set stop bits
}


const struct menu_command_t busvoodoo_uart_generic_commands[busvoodoo_uart_generic_commands_nb] = {
	{
		.shortcut = 'a',
		.name = "action",
		.command_description = "perform protocol actions",
		.argument = MENU_ARGUMENT_STRING,
		.argument_description = "[actions]",
		.command_handler = &busvoodoo_uart_generic_command_actions,
	},
	{
		.shortcut = 'r',
		.name = "receive",
		.command_description = "show incoming data [in hexadecimal or binary]",
		.argument = MENU_ARGUMENT_STRING,
		.argument_description = "[hex|bin]",
		.command_handler = &busvoodoo_uart_generic_command_receive,
	},
	{
		.shortcut = 't',
		.name = "transmit",
		.command_description = "transmit ASCII text (empty for CR+LF)",
		.argument = MENU_ARGUMENT_STRING,
		.argument_description = "[text]",
		.command_handler = &busvoodoo_uart_generic_command_transmit,
	},
	{
		.shortcut = 'x',
		.name = "transceive",
		.command_description = "transmit and receive data",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &busvoodoo_uart_generic_command_transceive,
	},
	{
		.shortcut = 'e',
		.name = "error",
		.command_description = "verify incoming transmission for errors",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &busvoodoo_uart_generic_command_error,
	},
	{
		.shortcut = 'd',
		.name = "detect",
		.command_description = "auto-detect serial configuration (baud rate, data bits, parity)",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &busvoodoo_uart_generic_command_detect,
	},
};

