diff --git a/lib/busvoodoo_rs485.c b/lib/busvoodoo_rs485.c
new file mode 100644
index 0000000..48489f4
--- /dev/null
+++ b/lib/busvoodoo_rs485.c
@@ -0,0 +1,648 @@
+/* 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 .
+ *
+ */
+/** BusVoodoo RS-485 mode (code)
+ * @file busvoodoo_rs485.c
+ * @author King Kévin
+ * @date 2018
+ * @note peripherals used: USART @ref busvoodoo_rs485_usart
+ */
+/* standard libraries */
+#include // standard integer types
+#include // standard utilities
+#include // string utilities
+
+/* STM32 (including CM3) libraries */
+#include // general purpose input output library
+#include // real-time control clock library
+#include // USART utilities
+
+/* own libraries */
+#include "global.h" // board definitions
+#include "print.h" // printing utilities
+#include "menu.h" // menu definitions
+#include "busvoodoo_global.h" // BusVoodoo definitions
+#include "busvoodoo_oled.h" // OLED utilities
+#include "busvoodoo_rs485.h" // own definitions
+
+/** @defgroup busvoodoo_rs485_usart USART peripheral used for RS-485/422 communication, using a RS-485/422 transceiver
+ * @{
+ */
+#define BUSVOODOO_RS485_USART 2 /**< USART peripheral */
+/** @} */
+
+/** mode setup stage */
+static enum busvoodoo_rs485_setting_t {
+ BUSVOODOO_RS485_SETTING_NONE,
+ BUSVOODOO_RS485_SETTING_BAUDRATE,
+ BUSVOODOO_RS485_SETTING_DATABITS,
+ BUSVOODOO_RS485_SETTING_PARITY,
+ BUSVOODOO_RS485_SETTING_STOPBITS,
+ BUSVOODOO_RS485_SETTING_DONE,
+} busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_NONE; /**< current mode setup stage */
+/** RS-485 baud rate (in bps) */
+static uint32_t busvoodoo_rs485_baudrate = 115200;
+/** RS-485 data bits */
+static uint8_t busvoodoo_rs485_databits = 8;
+/** RS-485 parity setting */
+static uint32_t busvoodoo_rs485_parity = USART_PARITY_NONE;
+/** RS-485 stop bits setting */
+static uint32_t busvoodoo_rs485_stopbits = USART_STOPBITS_1;
+
+/** setup RS-485 mode
+ * @param[out] prefix terminal prompt prefix
+ * @param[in] line terminal prompt line to configure mode
+ * @return if setup is complete
+ */
+static bool busvoodoo_rs485_setup(char** prefix, const char* line)
+{
+ bool complete = false; // is the setup complete
+ if (NULL==line) { // first call
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_NONE; // re-start configuration
+ }
+ switch (busvoodoo_rs485_setting) {
+ case BUSVOODOO_RS485_SETTING_NONE:
+ snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "baud rate in bps (1-2000000) [%u]", busvoodoo_rs485_baudrate);
+ *prefix = busvoodoo_global_string; // ask for baud rate
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_BAUDRATE;
+ break;
+ case BUSVOODOO_RS485_SETTING_BAUDRATE:
+ if (NULL==line || 0==strlen(line)) { // use default setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DATABITS; // go to next setting
+ } else { // setting provided
+ uint32_t baudrate = atoi(line); // parse setting
+ if (baudrate>0 && baudrate<=2000000) { // check setting
+ busvoodoo_rs485_baudrate = baudrate; // remember setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DATABITS; // go to next setting
+ }
+ }
+ if (BUSVOODOO_RS485_SETTING_DATABITS==busvoodoo_rs485_setting) { // if next setting
+ snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "data bits (8-9) [%u]", busvoodoo_rs485_databits); // prepare next setting
+ *prefix = busvoodoo_global_string; // display next setting
+ }
+ break;
+ case BUSVOODOO_RS485_SETTING_DATABITS:
+ if (NULL==line || 0==strlen(line)) { // use default setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_PARITY; // go to next setting
+ } else { // setting provided
+ uint8_t databits = atoi(line); // parse setting
+ if (8==databits || 9==databits) { // check setting
+ busvoodoo_rs485_databits = databits; // remember setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_PARITY; // go to next setting
+ }
+ }
+ if (BUSVOODOO_RS485_SETTING_PARITY==busvoodoo_rs485_setting) { // if next setting
+ printf("1) none\n");
+ printf("2) even\n");
+ printf("3) odd\n");
+ snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "parity (1,2,3) [%c]", USART_PARITY_NONE==busvoodoo_rs485_parity ? '1' : (USART_PARITY_EVEN==busvoodoo_rs485_parity ? '2' : '3')); // prepare next setting
+ *prefix = busvoodoo_global_string; // display next setting
+ }
+ break;
+ case BUSVOODOO_RS485_SETTING_PARITY:
+ if (NULL==line || 0==strlen(line)) { // use default setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_STOPBITS; // go to next setting
+ } else if (1==strlen(line)) { // setting provided
+ if ('1'==line[0]) { // no parity
+ busvoodoo_rs485_parity = USART_PARITY_NONE;
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_STOPBITS; // go to next setting
+ } else if ('2'==line[0]) { // even parity
+ busvoodoo_rs485_parity = USART_PARITY_EVEN;
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_STOPBITS; // go to next setting
+ } else if ('3'==line[0]) { // odd parity
+ busvoodoo_rs485_parity = USART_PARITY_ODD;
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_STOPBITS; // go to next setting
+ }
+ }
+ if (BUSVOODOO_RS485_SETTING_STOPBITS==busvoodoo_rs485_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) [%s]", USART_STOPBITS_0_5==busvoodoo_rs485_stopbits ? "0.5" : (USART_STOPBITS_1==busvoodoo_rs485_stopbits ? "1" : (USART_STOPBITS_1_5==busvoodoo_rs485_stopbits ? "1.5" : "2.0"))); // prepare next setting
+ *prefix = busvoodoo_global_string; // display next setting
+ }
+ break;
+ case BUSVOODOO_RS485_SETTING_STOPBITS:
+ if (NULL==line || 0==strlen(line)) { // use default setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DONE; // go to next setting
+ } else if (1==strlen(line)) { // setting provided
+ if ('1'==line[0]) { // 0.5 stop bits
+ busvoodoo_rs485_stopbits = USART_STOPBITS_0_5; // remember setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DONE; // go to next setting
+ } else if ('2'==line[0]) { // 1 stop bits
+ busvoodoo_rs485_stopbits = USART_STOPBITS_1; // remember setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DONE; // go to next setting
+ } else if ('3'==line[0]) { // 1.5 stop bits
+ busvoodoo_rs485_stopbits = USART_STOPBITS_1_5; // remember setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DONE; // go to next setting
+ } else if ('4'==line[0]) { // 2 stop bits
+ busvoodoo_rs485_stopbits = USART_STOPBITS_2; // remember setting
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_DONE; // go to next setting
+ }
+ }
+ if (BUSVOODOO_RS485_SETTING_DONE==busvoodoo_rs485_setting) { // we have all settings, configure RS-485
+ rcc_periph_clock_enable(RCC_AFIO); // enable clock for USART alternate function
+ rcc_periph_clock_enable(RCC_USART(BUSVOODOO_RS485_USART)); // enable clock for USART peripheral
+ usart_set_baudrate(USART(BUSVOODOO_RS485_USART), busvoodoo_rs485_baudrate); // set baud rate
+ usart_set_databits(USART(BUSVOODOO_RS485_USART), busvoodoo_rs485_databits); // set data bits
+ usart_set_parity(USART(BUSVOODOO_RS485_USART), busvoodoo_rs485_parity); // set parity
+ usart_set_stopbits(USART(BUSVOODOO_RS485_USART), busvoodoo_rs485_stopbits); // set stop bits
+ usart_set_flow_control(USART(BUSVOODOO_RS485_USART), USART_FLOWCONTROL_NONE); // set no flow control
+ usart_set_mode(USART(BUSVOODOO_RS485_USART), USART_MODE_TX_RX); // full-duplex communication
+ rcc_periph_clock_enable(RCC_USART_PORT(BUSVOODOO_RS485_USART)); // enable clock for USART GPIO peripheral
+ gpio_set_mode(USART_TX_PORT(BUSVOODOO_RS485_USART), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_TX_PIN(BUSVOODOO_RS485_USART)); // setup GPIO pin USART transmit
+ gpio_set_mode(USART_RX_PORT(BUSVOODOO_RS485_USART), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, USART_RX_PIN(BUSVOODOO_RS485_USART)); // setup GPIO pin USART receive (no need to pull since the transceiver will drive it)
+ gpio_clear(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set low to disable RS-485 transceiver drive
+ gpio_set_mode(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(BUSVOODOO_RS485_DE_PIN)); // setup RS-485 Drive Enable GPIO as output (pulled low to disable by default)
+ gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set RS-485 transceiver Receive Enable pin high to disable received
+ gpio_set_mode(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(BUSVOODOO_RS485_RE_PIN)); // setup RS-485 Receive Enable GPIO as output (pulled high to disable by default)
+ usart_enable(USART(BUSVOODOO_RS485_USART)); // enable USART
+ led_off(); // disable LED because there is no activity
+ busvoodoo_rs485_setting = BUSVOODOO_RS485_SETTING_NONE; // restart settings next time
+ *prefix = "RS-485"; // display mode
+ busvoodoo_oled_text_left(*prefix); // set mode title on OLED display
+ const char* pinout_io[10] = {"GND", "5V", "3V3", "LV", NULL, NULL, NULL, NULL, NULL, NULL}; // RS-485 mode I/O pinout
+ for (uint8_t i=0; i='0' && action[1]<='9') { // send decimal
+ return busvoodoo_rs485_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='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; i1) { // send binary value
+ for (uint32_t i=1; 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='1' && action[0]<='9') { // send decimal value
+ for (uint32_t i=1; 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=2 && ('"'==action[0] || '\''==action[0]) && (action[length-1]==action[0])) { // send ASCII character
+ if (!perform) {
+ return true;
+ }
+ for (uint32_t r=0; r0) {
+ 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;
+ }
+ }
+ printf("press any key to exit\n");
+ gpio_clear(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to low enable RS-485 transceiver receiver
+ while (!user_input_available) { // check for user input to exit
+ if ((USART_SR(USART(BUSVOODOO_RS485_USART)) & USART_SR_RXNE)) { // verify if data has been received
+ uint16_t c = usart_recv(USART(BUSVOODOO_RS485_USART)); // receive character
+ busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // enable blue LED to show reception
+ // remove unused bits (ignore parity bit)
+ if (USART_PARITY_NONE==busvoodoo_rs485_parity) { // no parity bit in frame
+ if (8==busvoodoo_rs485_databits) { // 8-bit frame
+ c &= 0xff;
+ } else { // 9-bit frame
+ c &= 0x1ff;
+ }
+ } else { // MSb is parity bit
+ if (8==busvoodoo_rs485_databits) { // 8-bit frame
+ c &= 0x7f;
+ } else { // 9-bit frame
+ c &= 0xff;
+ }
+ }
+ if (display_hex) { // display data in hex
+ if ((USART_PARITY_NONE==busvoodoo_rs485_parity) && 9==busvoodoo_rs485_databits) { // case where the final data is 9 bits long
+ printf("%03x ", c);
+ } else {
+ printf("%02x ", c);
+ }
+ } else if (display_bin) { // display data in binary
+ if (USART_PARITY_NONE==busvoodoo_rs485_parity) {
+ if (8==busvoodoo_rs485_databits) { // 8-bit frame
+ printf("%08b ", c);
+ } else { // 9-bit frame
+ printf("%09b ", c);
+ }
+ } else { // one bit is a parity bit
+ if (8==busvoodoo_rs485_databits) { // 8-bit frame
+ printf("%07b ", c);
+ } else { // 9-bit frame
+ printf("%08b ", c);
+ }
+ }
+ } else { // display in ASCII
+ printf("%c", c); // print received character
+ }
+ }
+ }
+ user_input_get(); // discard user input
+ gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to disable RS-485 transceiver receiver
+ printf("\n"); // get to next line
+}
+
+/** command to transmit and receive data
+ * @param[in] argument no argument required
+ */
+static void busvoodoo_rs485_command_transceive(void* argument)
+{
+ (void)argument; // we won't use the argument
+ 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
+ gpio_clear(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to low enable RS-485 transceiver receiver
+ 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
+ while ((0==(USART_SR(USART(BUSVOODOO_RS485_USART)) & USART_SR_TXE) && !user_input_available)); // wait for transmit buffer to be empty
+ if (USART_SR(USART(BUSVOODOO_RS485_USART)) & USART_SR_TXE) { // we can send a character
+ gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to disable RS-485 transceiver receiver
+ gpio_set(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set high to enable RS-485 transceiver driver
+ usart_send_blocking(USART(BUSVOODOO_RS485_USART), c); // send user character
+ busvoodoo_led_red_pulse(BUSVOODOO_LED_PULSE); // enable red LED to show transmission
+ while (0==(USART_SR(USART(BUSVOODOO_RS485_USART)) & USART_SR_TC)); // wait for transmission to be complete
+ gpio_clear(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set low to disable RS-485 transceiver driver
+ gpio_clear(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to low enable RS-485 transceiver receiver
+ }
+ } else { // user wants to exit
+ break; // exit infinite loop
+ }
+ }
+ if ((USART_SR(USART(BUSVOODOO_RS485_USART)) & USART_SR_RXNE)) { // verify if data has been received
+ char c = usart_recv(USART(BUSVOODOO_RS485_USART)); // receive character
+ busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // enable blue LED to show reception
+ printf("%c", c); // print received character
+ }
+ }
+ gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to disable RS-485 transceiver receiver
+ printf("\n"); // get to next line
+}
+
+/** command to verify incoming transmission for error
+ * @param[in] argument argument not required
+ */
+static void busvoodoo_rs485_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_rs485_read(); // read incoming data (this also checks for errors
+ }
+ user_input_get(); // discard user input
+}
+
+/** RS-485 menu commands */
+static const struct menu_command_t busvoodoo_rs485_commands[] = {
+ {
+ .shortcut = 'a',
+ .name = "action",
+ .command_description = "perform protocol actions",
+ .argument = MENU_ARGUMENT_STRING,
+ .argument_description = "[actions]",
+ .command_handler = &busvoodoo_rs485_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_rs485_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_rs485_command_transmit,
+ },
+ {
+ .shortcut = 'x',
+ .name = "transceive",
+ .command_description = "transmit and receive data",
+ .argument = MENU_ARGUMENT_NONE,
+ .argument_description = NULL,
+ .command_handler = &busvoodoo_rs485_command_transceive,
+ },
+ {
+ .shortcut = 'e',
+ .name = "error",
+ .command_description = "verify incoming transmission for errors",
+ .argument = MENU_ARGUMENT_NONE,
+ .argument_description = NULL,
+ .command_handler = &busvoodoo_rs485_command_error,
+ },
+};
+
+const struct busvoodoo_mode_t busvoodoo_rs485_mode = {
+ .name = "rs485",
+ .description = "Recommended Standard 485/422",
+ .full_only = true,
+ .setup = &busvoodoo_rs485_setup,
+ .commands = busvoodoo_rs485_commands,
+ .commands_nb = LENGTH(busvoodoo_rs485_commands),
+ .exit = &busvoodoo_rs485_exit,
+};
diff --git a/lib/busvoodoo_rs485.h b/lib/busvoodoo_rs485.h
new file mode 100644
index 0000000..26a4c48
--- /dev/null
+++ b/lib/busvoodoo_rs485.h
@@ -0,0 +1,23 @@
+/* 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 .
+ *
+ */
+/** BusVoodoo RS-485/422 mode (API)
+ * @file busvoodoo_rs485.h
+ * @author King Kévin
+ * @date 2018
+ * @note peripherals used: USART @ref busvoodoo_rs485_usart
+ */
+
+/** RS-485 mode interface definition */
+extern const struct busvoodoo_mode_t busvoodoo_rs485_mode;