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;