diff --git a/Makefile b/Makefile index 339ff25..8456317 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # General Target Settings TARGET = bluepill-serial-monster -SRCS = main.c system_clock.c status_led.c usb_core.c usb_descriptors.c usb_io.c usb_uid.c usb_panic.c usb_cdc.c +SRCS = main.c system_clock.c status_led.c usb_core.c usb_descriptors.c\ + usb_io.c usb_uid.c usb_panic.c usb_cdc.c cdc_shell.c gpio.c device_config.c # Toolchain & Utils CC = arm-none-eabi-gcc diff --git a/README.md b/README.md index 8057665..87797ad 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ board works with your computer, don't bother fixing it. * _DMA_ _RX_/_TX_ for high-speed communications; * _IDLE line_ detection for short response time; * Signed _INF_ driver for _Windows XP, 7, and 8_; +* Built-in command shell for device parameters configuration; * No external dependencies other than _CMSIS_; (1) _UART1_ does not support hardware flow control because _RTS_/_CTS_ pins @@ -68,7 +69,7 @@ or damage may occur.** Note: **5 V** tolerant input pins are shown **in bold**. -## Control Signals +## Control Signals (Default Configuration) **RTS**, **CTS**, **DSR**, **DTR**, **DCD** are **active-low** signals. @@ -89,6 +90,151 @@ by the host. Please take this behaviour into account if you rely on the _UART DMA RX/TX_ buffer size is **1024** bytes. +## Advanced Configuration + +_bluepill-serial-monster_ provides a configuration shell that allows +controlling various parameters of the UART signal lines. + +To access the configuration shell, open _UART1_ with any terminal emulator +application (such as _screen_, _Tera Term_, etc.) and connect **PB5** to ground. Serial port settings do not matter. + +You should see the configuration shell prompt: + +```text +******************************* +* Configuration Shell Started * +******************************* + +> +``` + +The configuration shell has minimal support for ANSI escape sequences. You can +use the arrow keys to move the cursor when editing a command, erase text with _Backspace_, and insert text anywhere in the command. You can also recall the +last command by pressing _UP_. + +Command and parameter names are case-sensitive. + +To get the list of available commands, type: + +```text +>help +``` + +To get command-specific help, type: + +```text +>help command-name +``` + +### UART Port Parameters + +UART port parameters can be viewed and set with the _uart_ command: + +```text +>help uart +uart: set and view UART parameters +Usage: uart port-number|all show|signal-name-1 param-1 value-1 ... [param-n value-n] [signal-name-2 ...] +Use "uart port-number|all show" to view current UART configuration. +Use "uart port-number|all signal-name-1 param-1 value-1 ... [param-n value-n] [signal-name-2 ...]" +to set UART parameters, where signal names are rx, tx, rts, cts, dsr, dtr, dcd, +and params are: + output [pp|od] + active [low|high] + pull [floating|up|down] +Example: "uart 1 tx output od" sets UART1 TX output type to open-drain +Example: "uart 3 rts active high dcd active high pull down" allows to set multiple parameters at once. +``` + +Changes to the UART parameters are applied instantly; however, the configuration +is not stored in the flash memory until you explicitly save it with: + +```text +>config save +``` + +To view current configuration of all UART ports, type: + +```text +>uart all show +``` + +To view current configuration of a particular UART port, type: + +```text +>uart port-number show +``` + +where _port-number_ is in range of 1 to 3. + +Output type can be set for any output signal. Available output types are: + +* **pp** for push-pull output; +* **od** for open-drain output; + +Example: + +```text +uart 1 tx output od +``` + +Pull type can be set for any input signal. Available pull types are: + +* **floating** for floating input; +* **up** for weak pull up; +* **down** for weak pull down; + +Example: + +```text +uart 1 dcd pull up +``` + +Signal polarity can be set for all input and output signals except for **RX**, +**TX**, and **CTS**. Available signal polarities are: + +* **low** for active-low signal polarity; +* **high** for active-high signal polarity; + +Example: + +```text +uart 1 rts active high +``` + +It is possible to set multiple signal parameters for multiple signals in one +command: + +```text +uart 1 tx output od rts output od active high +``` + +If the uart command encounters a syntax error or an invalid parameter in +the middle of a multiple parameters command line, it stops execution +immediately. However, it does not roll back valid parameters set before +the point where the error occured. + +It is also possible to set signal parameters for multiple ports in one command: + +```text +uart all tx output od +``` + +### Saving and Resetting Configuration + +To permanently save current device configuration, type: + +```text +config save +``` + +To reset the device to the default settings, type: + +```text +config reset +``` + +The default configuration is automatically stored in the flash memory after reset. + ## Flashing Firmware Download binary firmware from the diff --git a/cdc_config.h b/cdc_config.h new file mode 100644 index 0000000..045c843 --- /dev/null +++ b/cdc_config.h @@ -0,0 +1,21 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#ifndef CDC_CONFIG_H +#define CDC_CONFIG_H + +#include "gpio.h" +#include "usb_cdc.h" + +typedef struct { + gpio_pin_t pins[cdc_pin_last]; +} __attribute__ ((packed)) cdc_port_t; + +typedef struct { + cdc_port_t port_config[USB_CDC_NUM_PORTS]; +} __attribute__ ((packed)) cdc_config_t; + +#endif /* CDC_CONFIG_H */ diff --git a/cdc_shell.c b/cdc_shell.c new file mode 100644 index 0000000..2ab5756 --- /dev/null +++ b/cdc_shell.c @@ -0,0 +1,583 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#include +#include +#include +#include "usb_cdc.h" +#include "gpio.h" +#include "cdc_config.h" +#include "device_config.h" +#include "cdc_shell.h" + + +static const char *cdc_shell_banner = "\r\n\r\n" + "*******************************\r\n" + "* Configuration Shell Started *\r\n" + "*******************************\r\n\r\n"; +static const char *cdc_shell_prompt = ">"; +static const char *cdc_shell_new_line = "\r\n"; +static const char *cdc_shell_delim = "\t- "; + +static const char *escape_cursor_forward = "\033[C"; +static const char *escape_cursor_backward = "\033[D"; +static const char *escape_clear_line_to_end = "\033[0K"; + +typedef void (*cmd_func_t)(int argc, char *argv[]); + +typedef struct { + char *cmd; + cmd_func_t handler; + char *description; + char *usage; +} cdc_shell_cmd_t; + +/* Shell Helper Functions */ + +static int cdc_shell_invoke_command(int argc, char *argv[], const cdc_shell_cmd_t *commands) { + const cdc_shell_cmd_t *shell_cmd = commands; + while (shell_cmd->cmd) { + if (strcmp(shell_cmd->cmd, *argv) == 0) { + shell_cmd->handler(argc-1, argv+1); + return 0; + } + shell_cmd++; + } + return -1; +} + +/* Set Commands */ + +static const char *cdc_shell_err_uart_missing_arguments = "Error, no arguments, use \"help uart\" for the list of arguments.\r\n"; +static const char *cdc_shell_err_uart_invalid_uart = "Error, invalid UART number.\r\n"; +static const char *cdc_shell_err_uart_unknown_signal = "Error, unknown signal name.\r\n"; +static const char *cdc_shell_err_uart_missing_signame = "Error, expected \"show\" or a signal name, got nothing.\r\n"; +static const char *cdc_shell_err_uart_missing_params = "Error, missing signal parameters.\r\n"; +static const char *cdc_shell_err_uart_missing_output_type = "Error, missing output type.\r\n"; +static const char *cdc_shell_err_uart_invalid_output_type = "Error, invalid output type.\r\n"; +static const char *cdc_shell_err_uart_missing_polarity = "Error, missing polarity.\r\n"; +static const char *cdc_shell_err_uart_invalid_polarity = "Error, invalid polarity.\r\n"; +static const char *cdc_shell_err_uart_missing_pull_type = "Error, missing pull type.\r\n"; +static const char *cdc_shell_err_uart_invalid_pull_type = "Error, invalid pull type.\r\n"; +static const char *cdc_shell_err_cannot_set_output_type_for_input = "Error, cannot set output type for input pin.\r\n"; +static const char *cdc_shell_err_cannot_change_polarity = "Error, cannot change polarity of alternate function pins.\r\n"; +static const char *cdc_shell_err_cannot_set_pull_for_output = "Error, cannot pull type for output pin.\r\n"; + + +static const char *_cdc_uart_signal_names[cdc_pin_last] = { + "rx", "tx", "rts", "cts", "dsr", "dtr", "dcd", +}; + +static cdc_pin_t _cdc_uart_signal_by_name(char *name) { + for (int i = 0; i < sizeof(_cdc_uart_signal_names)/sizeof(*_cdc_uart_signal_names); i++) { + if (strcmp(name, _cdc_uart_signal_names[i]) == 0) { + return (cdc_pin_t)i; + } + } + return cdc_pin_unknown; +} + +static const char *_cdc_uart_output_types[gpio_output_last] = { + "pp", "od", +}; + +static gpio_output_t _cdc_uart_output_type_by_name(char *name) { + for (int i = 0; i< sizeof(_cdc_uart_output_types)/sizeof(*_cdc_uart_output_types); i++) { + if (strcmp(name, _cdc_uart_output_types[i]) == 0) { + return (gpio_output_t)i; + } + } + return gpio_output_unknown; +} + +static const char *_cdc_uart_polarities[gpio_polarity_last] = { + "high", "low", +}; + +static gpio_polarity_t _cdc_uart_polarity_by_name(char *name) { + for (int i = 0; i< sizeof(_cdc_uart_polarities)/sizeof(*_cdc_uart_polarities); i++) { + if (strcmp(name, _cdc_uart_polarities[i]) == 0) { + return (gpio_polarity_t)i; + } + } + return gpio_polarity_unknown; +} + +static const char *_cdc_uart_pull_types[gpio_pull_last] = { + "floating", "up", "down", +}; + +static gpio_pull_t _cdc_uart_pull_type_by_name(char *name) { + for (int i = 0; i< sizeof(_cdc_uart_pull_types)/sizeof(*_cdc_uart_pull_types); i++) { + if (strcmp(name, _cdc_uart_pull_types[i]) == 0) { + return (gpio_pull_t)i; + } + } + return gpio_pull_unknown; +} + +static void cdc_shell_cmd_uart_show(int port) { + const char *uart_str = "UART"; + const char *na_str = "n/a"; + const char *in_str = "in, "; + const char *out_str = "out, "; + const char *active_str = "active "; + const char *pull_str = "pull "; + const char *output_str = "output "; + const char *comma_str = ", "; + const char *colon_str = ":"; + char port_index_str[32]; + for (int port_index = ((port == -1) ? 0 : port); + port_index < ((port == -1) ? USB_CDC_NUM_PORTS : port + 1); + port_index++) { + const cdc_port_t *cdc_port = &device_config_get()->cdc_config.port_config[port_index]; + itoa(port_index+1, port_index_str, 10); + cdc_shell_write(uart_str, strlen(uart_str)); + cdc_shell_write(port_index_str, strlen(port_index_str)); + cdc_shell_write(colon_str, strlen(colon_str)); + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + for (cdc_pin_t pin = 0; pin < cdc_pin_last; pin++) { + const gpio_pin_t *cdc_pin = &cdc_port->pins[pin]; + const char *pin_name = _cdc_uart_signal_names[pin]; + cdc_shell_write(pin_name, strlen(pin_name)); + cdc_shell_write(cdc_shell_delim, strlen(cdc_shell_delim)); + if (cdc_pin->port) { + const char *active_value = _cdc_uart_polarities[cdc_pin->polarity]; + if (cdc_pin->dir == gpio_dir_input) { + cdc_shell_write(in_str, strlen(in_str)); + } else { + cdc_shell_write(out_str, strlen(output_str)); + } + cdc_shell_write(active_str, strlen(active_str)); + cdc_shell_write(active_value, strlen(active_value)); + cdc_shell_write(comma_str, strlen(comma_str)); + if (cdc_pin->dir == gpio_dir_input) { + const char *pull_value = _cdc_uart_pull_types[cdc_pin->pull]; + cdc_shell_write(pull_str, strlen(pull_str)); + cdc_shell_write(pull_value, strlen(pull_value)); + } else { + const char *output_value = _cdc_uart_output_types[cdc_pin->output]; + cdc_shell_write(output_str, strlen(output_str)); + cdc_shell_write(output_value, strlen(output_value)); + } + } else { + cdc_shell_write(na_str, strlen(na_str)); + } + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + } + } +} + +static int cdc_shell_cmd_uart_set_output_type(int port, cdc_pin_t uart_pin, gpio_output_t output) { + for (int port_index = ((port == -1) ? 0 : port); + port_index < ((port == -1) ? USB_CDC_NUM_PORTS : port + 1); + port_index++) { + gpio_pin_t *pin = &device_config_get()->cdc_config.port_config[port_index].pins[uart_pin]; + if (pin->dir == gpio_dir_output) { + pin->output = output; + usb_cdc_reconfigure_port_pin(port, uart_pin); + } else { + cdc_shell_write(cdc_shell_err_cannot_set_output_type_for_input, strlen(cdc_shell_err_cannot_set_output_type_for_input)); + return -1; + } + } + return 0; +} + +static int cdc_shell_cmd_uart_set_polarity(int port, cdc_pin_t uart_pin, gpio_polarity_t polarity) { + for (int port_index = ((port == -1) ? 0 : port); + port_index < ((port == -1) ? USB_CDC_NUM_PORTS : port + 1); + port_index++) { + gpio_pin_t *pin = &device_config_get()->cdc_config.port_config[port_index].pins[uart_pin]; + if (pin->func == gpio_func_general && (uart_pin != cdc_pin_rx) && (uart_pin != cdc_pin_cts)) { + pin->polarity = polarity; + usb_cdc_reconfigure_port_pin(port, uart_pin); + } else { + cdc_shell_write(cdc_shell_err_cannot_change_polarity, strlen(cdc_shell_err_cannot_change_polarity)); + return -1; + } + } + return 0; +} + +static int cdc_shell_cmd_uart_set_pull_type(int port, cdc_pin_t uart_pin, gpio_pull_t pull) { + for (int port_index = ((port == -1) ? 0 : port); + port_index < ((port == -1) ? USB_CDC_NUM_PORTS : port + 1); + port_index++) { + gpio_pin_t *pin = &device_config_get()->cdc_config.port_config[port_index].pins[uart_pin]; + if (pin->dir == gpio_dir_input) { + pin->pull = pull; + usb_cdc_reconfigure_port_pin(port, uart_pin); + } else { + cdc_shell_write(cdc_shell_err_cannot_set_pull_for_output, strlen(cdc_shell_err_cannot_set_pull_for_output)); + return -1; + } + } + return 0; +} + +static void cdc_shell_cmd_uart(int argc, char *argv[]) { + if (argc--) { + int port; + if (strcmp(*argv, "all") == 0) { + port = -1; + } else { + if (((port = atoi(*argv)) < 1) || port > USB_CDC_NUM_PORTS) { + cdc_shell_write(cdc_shell_err_uart_invalid_uart, strlen(cdc_shell_err_uart_invalid_uart)); + return; + } + port = port - 1; + } + argv++; + if (argc) { + if (strcmp(*argv, "show") == 0) { + cdc_shell_cmd_uart_show(port); + } else { + while(argc) { + argc--; + cdc_pin_t uart_pin = _cdc_uart_signal_by_name(*argv); + if (uart_pin == cdc_pin_unknown) { + cdc_shell_write(cdc_shell_err_uart_unknown_signal, strlen(cdc_shell_err_uart_unknown_signal)); + return; + } + argv++; + if (argc) { + while(argc) { + if (strcmp(*argv, "output") == 0) { + argc--; + argv++; + if (argc) { + gpio_output_t output_type = _cdc_uart_output_type_by_name(*argv); + if (output_type != gpio_output_unknown) { + argc--; + argv++; + if (cdc_shell_cmd_uart_set_output_type(port, uart_pin, output_type) == -1) { + return; + } + } else { + cdc_shell_write(cdc_shell_err_uart_invalid_output_type, strlen(cdc_shell_err_uart_invalid_output_type)); + return; + } + } else { + cdc_shell_write(cdc_shell_err_uart_missing_output_type, strlen(cdc_shell_err_uart_missing_output_type)); + return; + } + } else if (strcmp(*argv, "active") == 0) { + argc--; + argv++; + if (argc) { + gpio_polarity_t polarity = _cdc_uart_polarity_by_name(*argv); + if (polarity != gpio_polarity_unknown) { + argc--; + argv++; + if (cdc_shell_cmd_uart_set_polarity(port, uart_pin, polarity) == -1) { + return; + } + } else { + cdc_shell_write(cdc_shell_err_uart_invalid_polarity, strlen(cdc_shell_err_uart_invalid_polarity)); + return; + } + } else { + cdc_shell_write(cdc_shell_err_uart_missing_polarity, strlen(cdc_shell_err_uart_missing_polarity)); + return; + } + } else if (strcmp(*argv, "pull") == 0) { + argc--; + argv++; + if (argc) { + gpio_pull_t pull = _cdc_uart_pull_type_by_name(*argv); + if (pull != gpio_pull_unknown) { + argc--; + argv++; + if (cdc_shell_cmd_uart_set_pull_type(port, uart_pin, pull) == -1) { + return; + } + } else { + cdc_shell_write(cdc_shell_err_uart_invalid_pull_type, strlen(cdc_shell_err_uart_invalid_pull_type)); + return; + } + } else { + cdc_shell_write(cdc_shell_err_uart_missing_pull_type, strlen(cdc_shell_err_uart_missing_pull_type)); + return; + } + } else { + break; + } + } + } else { + cdc_shell_write(cdc_shell_err_uart_missing_params, strlen(cdc_shell_err_uart_missing_params)); + } + } + } + } else { + cdc_shell_write(cdc_shell_err_uart_missing_signame, strlen(cdc_shell_err_uart_missing_signame)); + } + } else { + cdc_shell_write(cdc_shell_err_uart_missing_arguments, strlen(cdc_shell_err_uart_missing_arguments)); + } +} + + +static const char *cdc_shell_err_config_missing_arguments = "Error, invalid or missing arguments, use \"help config\" for the list of arguments.\r\n"; + +static void cdc_shell_cmd_config_save() { + device_config_save(); +} + +static void cdc_shell_cmd_config_reset() { + device_config_reset(); + usb_cdc_reconfigure(); +} + +static void cdc_shell_cmd_config(int argc, char *argv[]) { + if (argc == 1) { + if (strcmp(*argv, "save") == 0) { + return cdc_shell_cmd_config_save(); + } + if (strcmp(*argv, "reset") == 0) { + return cdc_shell_cmd_config_reset(); + } + } + cdc_shell_write(cdc_shell_err_config_missing_arguments, strlen(cdc_shell_err_config_missing_arguments)); +} + +static void cdc_shell_cmd_help(int argc, char *argv[]); + +static const cdc_shell_cmd_t cdc_shell_commands[] = { + { + .cmd = "help", + .handler = cdc_shell_cmd_help, + .description = "shows this help message, use \"help command-name\" to get command-specific help", + .usage = "Usage: help [command-name]", + }, + { + .cmd = "config", + .handler = cdc_shell_cmd_config, + .description = "save and reset configuration paramters in the device flash memory", + .usage = "Usage: config save|reset\r\n" + "Use: \"config save\" to permanently save device configuration.\r\n" + "Use: \"config reset\" to reset device configuration to default.", + }, + { + .cmd = "uart", + .handler = cdc_shell_cmd_uart, + .description = "set and view UART parameters", + .usage = "Usage: uart port-number|all show|signal-name-1 param-1 value-1 ... [param-n value-n] [signal-name-2 ...]\r\n" + "Use \"uart port-number|all show\" to view current UART configuration.\r\n" + "Use \"uart port-number|all signal-name-1 param-1 value-1 ... [param-n value-n] [signal-name-2 ...]\"\r\n" + "to set UART parameters, where signal names are rx, tx, rts, cts, dsr, dtr, dcd,\r\n" + "and params are:\r\n" + " output\t[pp|od]\r\n" + " active\t[low|high]\r\n" + " pull\t\t[floating|up|down]\r\n" + "Example: \"uart 1 tx output od\" sets UART1 TX output type to open-drain\r\n" + "Example: \"uart 3 rts active high dcd active high pull down\" allows to set multiple parameters at once." + }, + { 0 } +}; + +/* Global Commands */ + +static const char *cdc_shell_err_no_help = "Error, no help for this command, use \"help\" to get the list of available commands.\r\n"; + +static void cdc_shell_cmd_help(int argc, char *argv[]) { + const cdc_shell_cmd_t *cmd = cdc_shell_commands; + while (cmd->cmd) { + if (argc) { + if (strcmp(*argv, cmd->cmd) == 0) { + const char *delim = ": "; + cdc_shell_write(cmd->cmd, strlen(cmd->cmd)); + cdc_shell_write(delim, strlen(delim)); + cdc_shell_write(cmd->description, strlen(cmd->description)); + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + cdc_shell_write(cmd->usage, strlen(cmd->usage)); + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + break; + } + } else { + cdc_shell_write(cmd->cmd, strlen(cmd->cmd)); + cdc_shell_write(cdc_shell_delim, strlen(cdc_shell_delim)); + cdc_shell_write(cmd->description, strlen(cmd->description)); + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + } + cmd++; + } + if (argc && (cmd->cmd == 0)) { + cdc_shell_write(cdc_shell_err_no_help, strlen(cdc_shell_err_no_help)); + } +} + +static const char *cdc_shell_err_unknown_command = "Error, unknown command, use \"help\" to get the list of available commands.\r\n"; + +static void cdc_shell_exec_command(int argc, char *argv[]) { + if (cdc_shell_invoke_command(argc, argv, cdc_shell_commands) == -1) { + cdc_shell_write(cdc_shell_err_unknown_command, strlen(cdc_shell_err_unknown_command)); + } +} + +static const char *cdc_shell_err_too_long = "Error, command line is too long.\r\n"; +static const char *cdc_shell_err_too_many_args = "Error, too many command line arguments.\r\n"; + +static void cdc_shell_parse_command_line(char *cmd_line) { + int argc = 0; + char *argv[USB_SHELL_MAC_CMD_ARGS]; + char *cmd_line_p = cmd_line; + while (isspace((int)(*cmd_line_p))) { + cmd_line_p++; + } + while (*cmd_line_p) { + if (argc < USB_SHELL_MAC_CMD_ARGS) { + argv[argc] = cmd_line_p; + while (*cmd_line_p && !isspace((int)(*cmd_line_p))) { + cmd_line_p++; + } + if (*cmd_line_p) { + *cmd_line_p++ = '\0'; + } + while (isspace((int)(*cmd_line_p))) { + cmd_line_p++; + } + argc++; + } else { + cdc_shell_write(cdc_shell_err_too_many_args, strlen(cdc_shell_err_too_many_args)); + return; + } + } + if (argc > 0) { + cdc_shell_exec_command(argc, argv); + } +} + +static char cmd_line_buf[USB_SHELL_MAX_CMD_LINE_SIZE]; +static char cmd_prev_line_buf[USB_SHELL_MAX_CMD_LINE_SIZE]; +static char *cmd_line_cursor = cmd_line_buf; + +static void cdc_shell_clear_cmd_buf() { + cmd_line_cursor = cmd_line_buf; + memset(cmd_line_buf, 0, sizeof(cmd_line_buf)); +} + +static enum { + cdc_shell_idle, + cdc_shell_expects_lf, + cdc_shell_expects_csi, + cdc_shell_expects_csn, +} cdc_shell_state; + +void cdc_shell_init() { + cdc_shell_clear_cmd_buf(); + memset(cmd_prev_line_buf, 0, sizeof(cmd_prev_line_buf)); + cdc_shell_state = cdc_shell_idle; + cdc_shell_write(cdc_shell_banner, strlen(cdc_shell_banner)); + cdc_shell_write(cdc_shell_prompt, strlen(cdc_shell_prompt)); +} + +#define ANSI_CTRLSEQ_ESCAPE_CHAR 0x1B +#define ANSI_CTRLSEQ_ESCAPE_CSI 0x5B +#define ANSI_CTRLSEQ_CUU 0x41 +#define ANSI_CTRLSEQ_CUD 0x42 +#define ANSI_CTRLSEQ_CUF 0x43 +#define ANSI_CTRLSEQ_CUB 0x44 + + +static void cdc_shell_cursor_move_back(int n_symb) { + if (n_symb) { + char n_symb_str[32]; + itoa(n_symb, n_symb_str, 10); + cdc_shell_write("\033[", 2); + cdc_shell_write(n_symb_str, strlen(n_symb_str)); + cdc_shell_write("D", 1); + } +} + +static void cdc_shell_handle_backspace() { + if (cmd_line_cursor > cmd_line_buf) { + cdc_shell_cursor_move_back(cmd_line_cursor - cmd_line_buf); + cdc_shell_write(escape_clear_line_to_end, strlen(escape_clear_line_to_end)); + cmd_line_cursor--; + memmove(cmd_line_cursor, cmd_line_cursor+1, strlen(cmd_line_cursor)); + cdc_shell_write(cmd_line_buf, strlen(cmd_line_buf)); + cdc_shell_cursor_move_back(strlen(cmd_line_buf) - (cmd_line_cursor - cmd_line_buf)); + } +} + +static void cdc_shell_insert_symbol(char c) { + memmove(cmd_line_cursor+1, cmd_line_cursor, strlen(cmd_line_cursor)+1); + *cmd_line_cursor = c; + cdc_shell_write(cmd_line_cursor, strlen(cmd_line_cursor)); + cmd_line_cursor++; + cdc_shell_cursor_move_back(strlen(cmd_line_buf) - (cmd_line_cursor - cmd_line_buf)); +} + +void cdc_shell_process_input(const void *buf, size_t count) { + const char *buf_p= buf; + while (count--) { + switch (cdc_shell_state) { + case cdc_shell_expects_csn: + if (isdigit((int)*buf_p)) { + /* Ignore values for simplicity */ + break; + } else { + if (*buf_p == ANSI_CTRLSEQ_CUF) { + if (*cmd_line_cursor) { + cmd_line_cursor++; + cdc_shell_write(escape_cursor_forward, strlen(escape_cursor_forward)); + } + } else if (*buf_p == ANSI_CTRLSEQ_CUB) { + if (cmd_line_cursor > cmd_line_buf) { + cmd_line_cursor--; + cdc_shell_write(escape_cursor_backward, strlen(escape_cursor_backward)); + } + } else if (*buf_p == ANSI_CTRLSEQ_CUU) { + size_t prev_cmd_len = strlen(cmd_prev_line_buf); + if (prev_cmd_len) { + strcpy(cmd_line_buf, cmd_prev_line_buf); + cdc_shell_cursor_move_back(cmd_line_cursor - cmd_line_buf); + cdc_shell_write(escape_clear_line_to_end, strlen(escape_clear_line_to_end)); + cmd_line_cursor = cmd_line_buf + prev_cmd_len; + cdc_shell_write(cmd_line_buf, prev_cmd_len); + } + } + cdc_shell_state = cdc_shell_idle; + } + break; + case cdc_shell_expects_csi: + if (*buf_p == ANSI_CTRLSEQ_ESCAPE_CSI) { + cdc_shell_state = cdc_shell_expects_csn; + break; + } + case cdc_shell_expects_lf: + cdc_shell_state = cdc_shell_idle; + if (*buf_p == '\n') { + break; + } + case cdc_shell_idle: + if (*buf_p == '\r' || *buf_p == '\n') { + cdc_shell_state = cdc_shell_expects_lf; + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + if (cmd_line_cursor != cmd_line_buf) { + strcpy(cmd_prev_line_buf, cmd_line_buf); + } + cdc_shell_parse_command_line(cmd_line_buf); + cdc_shell_clear_cmd_buf(); + cdc_shell_write(cdc_shell_prompt, strlen(cdc_shell_prompt)); + } else if (*buf_p == ANSI_CTRLSEQ_ESCAPE_CHAR) { + cdc_shell_state = cdc_shell_expects_csi; + } else if (*buf_p == '\b' || *buf_p == '\177') { + cdc_shell_handle_backspace(); + } else if (isprint((int)(*buf_p))) { + cdc_shell_insert_symbol(*buf_p); + if ((cmd_line_cursor - cmd_line_buf) >= sizeof(cmd_line_buf)/sizeof(*cmd_line_buf)) { + cdc_shell_clear_cmd_buf(); + cdc_shell_write(cdc_shell_new_line, strlen(cdc_shell_new_line)); + cdc_shell_write(cdc_shell_err_too_long, strlen(cdc_shell_err_too_long)); + cdc_shell_write(cdc_shell_prompt, strlen(cdc_shell_prompt)); + } + } + } + buf_p++; + } +} diff --git a/cdc_shell.h b/cdc_shell.h new file mode 100644 index 0000000..4e16db7 --- /dev/null +++ b/cdc_shell.h @@ -0,0 +1,20 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#ifndef CDC_SHELL_H +#define CDC_SHELL_H + +#include + +#define USB_SHELL_MAX_CMD_LINE_SIZE 0x100 +#define USB_SHELL_MAC_CMD_ARGS 0x10 + +extern void cdc_shell_write(const void *buf, size_t count); + +void cdc_shell_init(); +void cdc_shell_process_input(const void *buf, size_t count); + +#endif /* CDC_SHELL_H */ diff --git a/device_config.c b/device_config.c new file mode 100644 index 0000000..d86385a --- /dev/null +++ b/device_config.c @@ -0,0 +1,174 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#include +#include +#include +#include "device_config.h" + +#define DEVICE_CONFIG_FLASH_SIZE 0x10000UL +#define DEVICE_CONFIG_NUM_PAGES 2 +#define DEVICE_CONFIG_PAGE_SIZE 0x400UL +#define DEVICE_CONFIG_FLASH_END (FLASH_BASE + DEVICE_CONFIG_FLASH_SIZE) +#define DEVICE_CONFIG_BASE_ADDR ((void*)(DEVICE_CONFIG_FLASH_END - DEVICE_CONFIG_NUM_PAGES * DEVICE_CONFIG_PAGE_SIZE)) +#define DEVICE_CONFIG_MAGIC 0xDECFDECFUL + +static const device_config_t default_device_config = { + .config_pin = { .port = GPIOB, .pin = 5, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + .cdc_config = { + .port_config = { + /* Port 0 */ + { + .pins = + { + /* rx */ { .port = GPIOA, .pin = 10, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_high }, + /* tx */ { .port = GPIOA, .pin = 9, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_alternate, .output = gpio_output_pp, .polarity = gpio_polarity_high }, + /* rts */ { .port = 0 }, /* No RTS due to the below reason */ + /* cts */ { .port = 0 }, /* CTS pin is occupied by USB */ + /* dsr */ { .port = GPIOB, .pin = 7, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + /* dtr */ { .port = GPIOA, .pin = 4, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_general, .output = gpio_output_pp, .polarity = gpio_polarity_low }, + /* dcd */ { .port = GPIOB, .pin = 15, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + } + }, + /* Port 1 */ + { + .pins = + { + /* rx */ { .port = GPIOA, .pin = 3, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_high }, + /* tx */ { .port = GPIOA, .pin = 2, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_alternate, .output = gpio_output_pp, .polarity = gpio_polarity_high }, + /* rts */ { .port = GPIOA, .pin = 1, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_general, .output = gpio_output_pp, .polarity = gpio_polarity_low}, + /* cts */ { .port = GPIOA, .pin = 0, .dir = gpio_dir_input, .pull = gpio_pull_down, .polarity = gpio_polarity_low }, + /* dsr */ { .port = GPIOB, .pin = 4, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + /* dtr */ { .port = GPIOA, .pin = 5, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_general, .output = gpio_output_pp, .polarity = gpio_polarity_low }, + /* dcd */ { .port = GPIOB, .pin = 8, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + } + }, + /* Port 2 */ + { + .pins = + { + /* rx */ { .port = GPIOB, .pin = 11, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_high }, + /* tx */ { .port = GPIOB, .pin = 10, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_alternate, .output = gpio_output_pp, .polarity = gpio_polarity_high }, + /* rts */ { .port = GPIOB, .pin = 14, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_general, .output = gpio_output_pp, .polarity = gpio_polarity_low }, + /* cts */ { .port = GPIOB, .pin = 13, .dir = gpio_dir_input, .pull = gpio_pull_down, .polarity = gpio_polarity_low }, + /* dsr */ { .port = GPIOB, .pin = 6, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + /* dtr */ { .port = GPIOA, .pin = 6, .dir = gpio_dir_output, .speed = gpio_speed_medium, .func = gpio_func_general, .output = gpio_output_pp, .polarity = gpio_polarity_low }, + /* dcd */ { .port = GPIOB, .pin = 9, .dir = gpio_dir_input, .pull = gpio_pull_up, .polarity = gpio_polarity_low }, + } + }, + } + } +}; + +static device_config_t current_device_config; + +static uint32_t device_config_calc_crc(const device_config_t *device_config) { + uint32_t *word_p = (uint32_t*)device_config; + size_t bytes_left = offsetof(device_config_t, crc); + CRC->CR |= CRC_CR_RESET; + while (bytes_left > sizeof(*word_p)) { + CRC->DR = *word_p++; + bytes_left -= sizeof(*word_p); + } + if (bytes_left) { + uint32_t shift = 0; + uint32_t tail = 0; + uint8_t *byte_p = (uint8_t*)word_p; + for (int i = 0; i < bytes_left; i++) { + tail |= (uint32_t)(*byte_p++) << (shift); + shift += CHAR_BIT; + } + CRC->DR = tail; + } + return CRC->DR; +} + +const static device_config_t* device_config_get_stored() { + uint8_t *config_page = (uint8_t*)DEVICE_CONFIG_BASE_ADDR; + size_t config_pages = DEVICE_CONFIG_NUM_PAGES; + while (config_pages--) { + const device_config_t *stored_config = (device_config_t*)config_page; + if ((stored_config->magic == DEVICE_CONFIG_MAGIC) && + (device_config_calc_crc(stored_config) == stored_config->crc)) { + return stored_config; + } + config_page += DEVICE_CONFIG_PAGE_SIZE; + } + return 0; +} + +void device_config_init() { + RCC->AHBENR |= RCC_AHBENR_CRCEN; + const device_config_t *stored_config = device_config_get_stored(); + if (stored_config == 0) { + stored_config = &default_device_config; + } + memcpy(¤t_device_config, stored_config, sizeof(*stored_config)); +} +device_config_t *device_config_get() { + return ¤t_device_config; +} + +void device_config_save() { + uint16_t *last_config_magic = 0; + uint8_t *config_page = (uint8_t*)DEVICE_CONFIG_BASE_ADDR; + size_t config_pages = DEVICE_CONFIG_NUM_PAGES; + uint16_t *src_word_p = (uint16_t*)¤t_device_config; + uint16_t *dst_word_p; + size_t bytes_left = sizeof(current_device_config); + while (config_pages--) { + const device_config_t *stored_config = (device_config_t*)config_page; + if ((stored_config->magic == DEVICE_CONFIG_MAGIC) && + (device_config_calc_crc(stored_config) == stored_config->crc)) { + last_config_magic = (uint16_t*)&stored_config->magic; + } else { + break; + } + config_page += DEVICE_CONFIG_PAGE_SIZE; + } + if (config_pages == 0) { + config_page = (uint8_t*)DEVICE_CONFIG_BASE_ADDR; + } + dst_word_p = (uint16_t*)config_page; + if (FLASH->CR & FLASH_CR_LOCK) { + FLASH->KEYR = 0x45670123; + FLASH->KEYR = 0xCDEF89AB; + } + while (FLASH->SR & FLASH_SR_BSY); + FLASH->SR = FLASH->SR & FLASH_SR_EOP; + FLASH->CR = FLASH_CR_PER; + FLASH->AR = (uint32_t)config_page; + FLASH->CR |= FLASH_CR_STRT; + while (!(FLASH->SR & FLASH_SR_EOP)); + FLASH->SR = FLASH_SR_EOP; + FLASH->CR &= ~FLASH_CR_PER; + current_device_config.magic = DEVICE_CONFIG_MAGIC; + current_device_config.crc = device_config_calc_crc(¤t_device_config); + FLASH->CR |= FLASH_CR_PG; + while (bytes_left > 1) { + *dst_word_p++ = *src_word_p++; + while (!(FLASH->SR & FLASH_SR_EOP)); + FLASH->SR = FLASH_SR_EOP; + bytes_left -= sizeof(*dst_word_p); + } + if (bytes_left) { + *dst_word_p = (uint16_t)(*(uint8_t*)src_word_p); + while (!(FLASH->SR & FLASH_SR_EOP)); + FLASH->SR = FLASH_SR_EOP; + } + if (last_config_magic) { + *last_config_magic = 0x0000; + while (!(FLASH->SR & FLASH_SR_EOP)); + FLASH->SR = FLASH_SR_EOP; + } + FLASH->CR &= ~(FLASH_CR_PG); + FLASH->CR |= FLASH_CR_LOCK; +} + +void device_config_reset() { + memcpy(¤t_device_config, &default_device_config, sizeof(default_device_config)); + device_config_save(); +} \ No newline at end of file diff --git a/device_config.h b/device_config.h new file mode 100644 index 0000000..7fff633 --- /dev/null +++ b/device_config.h @@ -0,0 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#ifndef DEVICE_CONFIG_H +#define DEVICE_CONFIG_H + +#include +#include "gpio.h" +#include "cdc_config.h" + +typedef struct { + uint32_t magic; + gpio_pin_t config_pin; + cdc_config_t cdc_config; + uint32_t crc; /* should be the last member of the struct */ +} __attribute__ ((packed, aligned(4))) device_config_t; + +void device_config_init(); +device_config_t *device_config_get(); + +void device_config_save(); +void device_config_reset(); + +#endif /* DEVICE_CONFIG_H_ */ diff --git a/gpio.c b/gpio.c new file mode 100644 index 0000000..52abcbd --- /dev/null +++ b/gpio.c @@ -0,0 +1,64 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#include "gpio.h" + +static void _gpio_enable_port(GPIO_TypeDef *port) { + int portnum = (((uint32_t)port - GPIOA_BASE) / (GPIOB_BASE - GPIOA_BASE)); + RCC->APB2ENR |= RCC_APB2ENR_IOPAEN << portnum; +} + +void gpio_pin_init(const gpio_pin_t *pin) { + if (pin->port) { + volatile uint32_t *crx = &pin->port->CRL + (pin->pin >> 3); + uint8_t crx_offset = (pin->pin & 0x07) << 2; + uint32_t modecfg = 0; + _gpio_enable_port(pin->port); + *crx &= ~((GPIO_CRL_CNF0 | GPIO_CRL_MODE0) << crx_offset); + if (pin->dir == gpio_dir_input) { + if (pin->pull == gpio_pull_floating) { + modecfg |= GPIO_CRL_CNF0_0; + } else { + modecfg |= GPIO_CRL_CNF0_1; + pin->port->BSRR = ((pin->pull == gpio_pull_up) ? GPIO_BSRR_BS0 : GPIO_BSRR_BR0) << pin->pin; + } + } else { + switch (pin->speed) { + case gpio_speed_unknown: + case gpio_speed_low: + modecfg |= GPIO_CRL_MODE0_1; + break; + case gpio_speed_medium: + modecfg |= GPIO_CRL_MODE0_0; + break; + case gpio_speed_high: + modecfg |= GPIO_CRL_MODE0; + break; + } + if (pin->output == gpio_output_od) { + modecfg |= GPIO_CRL_CNF0_0; + } + if (pin->func == gpio_func_alternate) { + modecfg |= GPIO_CRL_CNF0_1; + } + } + *crx |= (modecfg << crx_offset); + } +} + +void gpio_pin_set(const gpio_pin_t *pin, int is_active) { + if (pin->port) { + pin->port->BSRR = (GPIO_BSRR_BS0 << pin->pin) + << (!!is_active != (pin->polarity == gpio_polarity_low) ? 0 : GPIO_BSRR_BR0_Pos); + } +} + +int gpio_pin_get(const gpio_pin_t *pin) { + if (pin->port) { + return (!!(pin->port->IDR & (GPIO_IDR_IDR0 << pin->pin))) != (pin->polarity == gpio_polarity_low); + } + return 0; +} diff --git a/gpio.h b/gpio.h new file mode 100644 index 0000000..490a896 --- /dev/null +++ b/gpio.h @@ -0,0 +1,73 @@ +/* + * MIT License + * + * Copyright (c) 2020 Kirill Kotyagin + */ + +#ifndef GPIO_H +#define GPIO_H + +#include +#include + +typedef enum { + gpio_dir_input, + gpio_dir_output, + gpio_dir_unknown, + gpio_dir_last = gpio_dir_unknown +} __attribute__ ((packed)) gpio_dir_t; + +typedef enum { + gpio_func_general, + gpio_func_alternate, + gpio_func_unknown, + gpio_func_last = gpio_func_unknown +} __attribute__ ((packed)) gpio_func_t; + +typedef enum { + gpio_output_pp, + gpio_output_od, + gpio_output_unknown, + gpio_output_last = gpio_output_unknown +} __attribute__ ((packed)) gpio_output_t; + +typedef enum { + gpio_pull_floating, + gpio_pull_up, + gpio_pull_down, + gpio_pull_unknown, + gpio_pull_last = gpio_pull_unknown +} __attribute__ ((packed)) gpio_pull_t; + +typedef enum { + gpio_polarity_high, + gpio_polarity_low, + gpio_polarity_unknown, + gpio_polarity_last = gpio_polarity_unknown, +} __attribute__ ((packed)) gpio_polarity_t; + +typedef enum { + gpio_speed_low, + gpio_speed_medium, + gpio_speed_high, + gpio_speed_unknown, + gpio_speed_last = gpio_speed_unknown +} __attribute__ ((packed)) gpio_speed_t; + +typedef struct { + GPIO_TypeDef* port; + uint8_t pin; + gpio_dir_t dir; + gpio_func_t func; + gpio_output_t output; + gpio_pull_t pull; + gpio_polarity_t polarity; + gpio_speed_t speed; +} __attribute__ ((packed)) gpio_pin_t; + +void gpio_pin_init(const gpio_pin_t *pin); + +void gpio_pin_set(const gpio_pin_t *pin, int is_active); +int gpio_pin_get(const gpio_pin_t *pin); + +#endif /* GPIO_G */ diff --git a/main.c b/main.c index c1710d7..58d77e7 100644 --- a/main.c +++ b/main.c @@ -7,11 +7,13 @@ #include #include "system_clock.h" #include "status_led.h" +#include "device_config.h" #include "usb.h" int main() { system_clock_init(); + device_config_init(); status_led_init(); usb_init(); while (1) { diff --git a/usb_cdc.c b/usb_cdc.c index bf2f41d..1746770 100644 --- a/usb_cdc.c +++ b/usb_cdc.c @@ -11,11 +11,15 @@ #include "usb_core.h" #include "usb_descriptors.h" #include "usb_panic.h" +#include "cdc_shell.h" +#include "device_config.h" +#include "gpio.h" #include "usb_cdc.h" /* USB CDC Device Enabled Flag */ static uint8_t usb_cdc_enabled = 0; +static uint8_t usb_cdc_config_mode = 0; /* USB CDC State Struct */ @@ -39,6 +43,7 @@ typedef struct { usb_cdc_serial_state_t serial_state; uint8_t serial_state_pending; uint8_t rts_active; + uint8_t dtr_active; } usb_cdc_state_t; static usb_cdc_state_t usb_cdc_states[USB_CDC_NUM_PORTS]; @@ -195,44 +200,47 @@ static void usb_cdc_notify_port_parity_error(int port) { /* Line State and Coding */ -static void usb_cdc_set_port_dtr(int port, int on) { +static void usb_cdc_update_port_dtr(int port) { if (port < USB_CDC_NUM_PORTS) { - const uint32_t base_dtr_pin = GPIO_BSRR_BS4; - GPIOA->BSRR = (base_dtr_pin << port) << (on ? GPIO_BSRR_BR0_Pos : 0); + usb_cdc_state_t *cdc_state = &usb_cdc_states[port]; + const gpio_pin_t *dtr_pin = &device_config_get()->cdc_config.port_config[port].pins[cdc_pin_dtr]; + gpio_pin_set(dtr_pin, cdc_state->dtr_active); + } +} + +static void usb_cdc_set_port_dtr(int port, int dtr_active) { + if (port < USB_CDC_NUM_PORTS) { + usb_cdc_state_t *cdc_state = &usb_cdc_states[port]; + cdc_state->dtr_active = dtr_active; + usb_cdc_update_port_dtr(port); } } static void usb_cdc_update_port_rts(int port) { if ((port < USB_CDC_NUM_PORTS) && (port != 0)) { + const gpio_pin_t *rts_pin = &device_config_get()->cdc_config.port_config[port].pins[cdc_pin_rts]; usb_cdc_state_t *cdc_state = &usb_cdc_states[port]; circ_buf_t *rx_buf = &cdc_state->rx_buf; - int on = circ_buf_space(rx_buf->head, rx_buf->tail, USB_CDC_BUF_SIZE) > (USB_CDC_BUF_SIZE>>1) && cdc_state->rts_active; - if (port == 1) { - GPIOA->BSRR = GPIO_BSRR_BS1 << (on ? GPIO_BSRR_BR0_Pos : 0); - } else if (port == 2) { - GPIOB->BSRR = GPIO_BSRR_BS14 << (on ? GPIO_BSRR_BR0_Pos : 0); - } + int rts_active = (circ_buf_space(rx_buf->head, rx_buf->tail, USB_CDC_BUF_SIZE) > (USB_CDC_BUF_SIZE>>1)) && cdc_state->rts_active; + gpio_pin_set(rts_pin, rts_active); } } -static void usb_cdc_set_port_rts(int port, int on) { +static void usb_cdc_set_port_rts(int port, int rts_active) { if ((port < USB_CDC_NUM_PORTS)) { - usb_cdc_states[port].rts_active = on; + usb_cdc_states[port].rts_active = rts_active; usb_cdc_update_port_rts(port); } } static usb_status_t usb_cdc_set_control_line_state(int port, uint16_t state) { - usb_cdc_set_port_dtr(port, state & USB_CDC_CONTROL_LINE_STATE_DTR_MASK); + usb_cdc_set_port_dtr(port, (state & USB_CDC_CONTROL_LINE_STATE_DTR_MASK)); usb_cdc_set_port_rts(port, (state & USB_CDC_CONTROL_LINE_STATE_RTS_MASK)); return usb_status_ack; } static usb_status_t usb_cdc_set_line_coding(int port, const usb_cdc_line_coding_t *line_coding, int dry_run) { USART_TypeDef *usart = usb_cdc_get_port_usart(port); - if (line_coding->dwDTERate != 9600) { - __NOP(); - } if (line_coding->dwDTERate != 0) { uint32_t new_brr = usb_cdc_get_port_fck(port) / line_coding->dwDTERate; if (!dry_run) { @@ -350,6 +358,72 @@ static void usb_cdc_port_rx_interrupt(int port) { } } +/* Configuration Mode Handling */ + +void usb_cdc_config_mode_enter() { + usb_cdc_state_t *cdc_state = &usb_cdc_states[USB_CDC_CONFIG_PORT]; + USART_TypeDef *usart = usb_cdc_get_port_usart(USB_CDC_CONFIG_PORT); + DMA_Channel_TypeDef *dma_tx_ch = usb_cdc_get_port_dma_channel(USB_CDC_CONFIG_PORT, usb_cdc_port_direction_tx); + cdc_state->rx_buf.tail = cdc_state->rx_buf.head = 0; + cdc_state->tx_buf.tail = cdc_state->tx_buf.head = 0; + usart->CR1 &= ~(USART_CR1_RE); + dma_tx_ch->CCR &= ~(DMA_CCR_EN); + cdc_shell_init(); + usb_cdc_config_mode = 1; +} + +void usb_cdc_config_mode_leave() { + usb_cdc_state_t *cdc_state = &usb_cdc_states[USB_CDC_CONFIG_PORT]; + DMA_Channel_TypeDef *dma_rx_ch = usb_cdc_get_port_dma_channel(USB_CDC_CONFIG_PORT, usb_cdc_port_direction_rx); + size_t dma_head = USB_CDC_BUF_SIZE - dma_rx_ch->CNDTR; + USART_TypeDef *usart = usb_cdc_get_port_usart(USB_CDC_CONFIG_PORT); + cdc_state->rx_buf.tail = cdc_state->rx_buf.head = dma_head; + cdc_state->tx_buf.tail = cdc_state->tx_buf.head = 0; + usart->CR1 |= USART_CR1_RE; + usb_cdc_config_mode = 0; +} + +/* + * USB_CDC_CONFIG_PORT buffers are reused for the config shell. + * usb_cdc_config_process_tx must ensure enough tx buf space is available + * for the next usb transfer. + */ + +void usb_cdc_config_mode_process_tx() { + uint8_t ep_num = usb_cdc_get_port_data_ep(USB_CDC_CONFIG_PORT); + usb_cdc_state_t *cdc_state = &usb_cdc_states[USB_CDC_CONFIG_PORT]; + circ_buf_t *tx_buf = &cdc_state->tx_buf; + size_t count; + if (usb_bytes_available(ep_num) < circ_buf_space(tx_buf->head, tx_buf->tail, USB_CDC_BUF_SIZE)) { + usb_circ_buf_read(ep_num, tx_buf, USB_CDC_BUF_SIZE); + } else { + usb_panic(); + } + while((count = circ_buf_count_to_end(tx_buf->head, tx_buf->tail, USB_CDC_BUF_SIZE))) { + cdc_shell_process_input(&tx_buf->data[tx_buf->tail], count); + tx_buf->tail = (tx_buf->tail + count) & (USB_CDC_BUF_SIZE - 1); + } +} + +void cdc_shell_write(const void *buf, size_t count) { + usb_cdc_state_t *cdc_state = &usb_cdc_states[USB_CDC_CONFIG_PORT]; + circ_buf_t *rx_buf = &cdc_state->rx_buf; + while (count) { + size_t bytes_to_copy; + size_t space_available = circ_buf_space_to_end(rx_buf->head, rx_buf->tail, USB_CDC_BUF_SIZE); + if (space_available == 0) { + rx_buf->tail = rx_buf->head; + space_available = circ_buf_space_to_end(rx_buf->head, rx_buf->tail, USB_CDC_BUF_SIZE); + } + bytes_to_copy = (space_available > count) ? count : space_available; + memcpy(&rx_buf->data[rx_buf->head], buf, bytes_to_copy); + rx_buf->head = (rx_buf->head + bytes_to_copy) & (USB_CDC_BUF_SIZE - 1); + count -= bytes_to_copy; + buf = (uint8_t*)buf + bytes_to_copy; + } + usb_cdc_port_send_rx_usb(USB_CDC_CONFIG_PORT); +} + /* USB USART TX Functions */ static void usb_cdc_port_start_tx(int port) { @@ -390,7 +464,11 @@ static void usb_cdc_port_tx_complete(int port) { cdc_state->usb_rx_pending_ep = 0; } } - usb_cdc_port_start_tx(port); + if ((port != USB_CDC_CONFIG_PORT) || !usb_cdc_config_mode) { + usb_cdc_port_start_tx(port); + } else { + usb_cdc_config_mode_process_tx(port); + } } /* DMA Interrupt Handlers */ @@ -442,8 +520,8 @@ void DMA1_Channel2_IRQHandler() { static void usb_cdc_usart_irq_handler(int port) { USART_TypeDef *usart = usb_cdc_get_port_usart(port); uint32_t wait_rxne = 0; - volatile uint32_t status = usart->SR; - volatile uint32_t dr; + uint32_t status = usart->SR; + uint32_t dr; if (status & USART_SR_PE) { wait_rxne = 1; usb_cdc_notify_port_parity_error(port); @@ -474,9 +552,38 @@ void USART3_IRQHandler() { usb_cdc_usart_irq_handler(2); } +/* Port Configuration & Control Lines Functions */ + +void usb_cdc_reconfigure_port_pin(int port, cdc_pin_t pin) { + if (port < USB_CDC_NUM_PORTS && pin < cdc_pin_last) { + gpio_pin_init(&device_config_get()->cdc_config.port_config[port].pins[pin]); + if (pin == cdc_pin_rts) { + usb_cdc_update_port_rts(port); + } else if (pin == cdc_pin_dtr) { + usb_cdc_update_port_dtr(port); + } + } +} + +static void usb_cdc_configure_port(int port) { + const device_config_t *device_config = device_config_get(); + for (cdc_pin_t pin = 0; pin < cdc_pin_last; pin++) { + gpio_pin_init(&device_config->cdc_config.port_config[port].pins[pin]); + usb_cdc_update_port_rts(port); + usb_cdc_update_port_dtr(port); + } +} + +void usb_cdc_reconfigure() { + for (int port = 0; port < USB_CDC_NUM_PORTS; port++) { + usb_cdc_configure_port(port); + } +} + /* Device Lifecycle */ void usb_cdc_reset() { + const device_config_t *device_config = device_config_get(); usb_cdc_enabled = 0; NVIC_EnableIRQ(DMA1_Channel2_IRQn); NVIC_EnableIRQ(DMA1_Channel3_IRQn); @@ -484,52 +591,18 @@ void usb_cdc_reset() { NVIC_EnableIRQ(DMA1_Channel5_IRQn); NVIC_EnableIRQ(DMA1_Channel6_IRQn); NVIC_EnableIRQ(DMA1_Channel7_IRQn); - /* USART TX/RTS Pins */ - RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; - RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN; - GPIOA->CRH &= ~(GPIO_CRH_CNF9); - GPIOA->CRH |= (GPIO_CRH_MODE9_0|GPIO_CRH_CNF9_1); - GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_CNF2); - GPIOA->CRL |= ((GPIO_CRL_MODE1_0) | (GPIO_CRL_MODE2_0|GPIO_CRL_CNF2_1)); - GPIOB->CRH &= ~(GPIO_CRH_CNF10 | GPIO_CRH_CNF14); - GPIOB->CRH |= ((GPIO_CRH_MODE10_0|GPIO_CRH_CNF10_1) | (GPIO_CRH_MODE14_0)); - /* USART RX Pins Pull Ups */ - GPIOA->CRL &= ~(GPIO_CRL_CNF3); - GPIOA->CRL |= (GPIO_CRL_CNF3_1); - GPIOA->CRH &= ~(GPIO_CRH_CNF10); - GPIOA->CRH |= (GPIO_CRH_CNF10_1); - GPIOA->ODR |= (GPIO_ODR_ODR3 | GPIO_ODR_ODR10); - GPIOB->CRH &= ~(GPIO_CRH_CNF11); - GPIOB->CRH |= (GPIO_CRH_CNF11_1); - GPIOB->ODR |= (GPIO_ODR_ODR11); - /* RTS Initial Value */ - GPIOA->BSRR = GPIO_BSRR_BS1; - GPIOB->BSRR = GPIO_BSRR_BS14; - /* USART CTS Pull Down */ - GPIOA->CRL &= ~(GPIO_CRL_CNF0); - GPIOA->CRL |= (GPIO_CRL_CNF0_1); - GPIOB->CRH &= ~(GPIO_CRH_CNF13); - GPIOB->CRH |= (GPIO_CRH_CNF13_1); - /* USART DTR Pins */ - GPIOA->CRL &= ~(GPIO_CRL_CNF4 | GPIO_CRL_CNF5 | GPIO_CRL_CNF6); - GPIOA->CRL |= (GPIO_CRL_MODE4_0 | GPIO_CRL_MODE5_0 | GPIO_CRL_MODE6_0); - GPIOA->BSRR = (GPIO_BSRR_BS4 | GPIO_BSRR_BS5 | GPIO_BSRR_BS6); /* * Disable JTAG interface (SWD is still enabled), * this frees PA15, PB3, PB4 (needed for DSR inputs). */ RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; + AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE; + /* Configuration Mode Pin */ + gpio_pin_init(&device_config->config_pin); + /* USART & DMA Reset and Setup */ + RCC->APB2ENR |= RCC_APB2ENR_USART1EN; + RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN; RCC->AHBENR |= RCC_AHBENR_DMA1EN; - /* DSR/DCD inputs configuration */ - GPIOB->CRL &= ~(GPIO_CRL_CNF4 | GPIO_CRL_CNF6 | GPIO_CRL_CNF7); - GPIOB->CRL |= ( GPIO_CRL_CNF4_1 | GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_1); - GPIOB->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_CNF9 | GPIO_CRH_CNF15); - GPIOB->CRH |= (GPIO_CRH_CNF8_1 | GPIO_CRH_CNF9_1 | GPIO_CRH_CNF15_1); - GPIOB->ODR |= ( - GPIO_ODR_ODR15 | GPIO_ODR_ODR4 | GPIO_ODR_ODR6 | - GPIO_ODR_ODR7 | GPIO_ODR_ODR8 | GPIO_ODR_ODR9 - ); - /* USART Reset and Setup */ RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST; RCC->APB1RSTR |= RCC_APB1RSTR_USART2RST; RCC->APB1RSTR |= RCC_APB1RSTR_USART3RST; @@ -540,6 +613,7 @@ void usb_cdc_reset() { for (int port=0; portcdc_config.port_config[port]; usb_cdc_serial_state_t state = usb_cdc_states[port].serial_state; state &= ~(USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_DCD); - switch (port) { - case 0: - if ((GPIOB->IDR & GPIO_IDR_IDR7) == 0) { - state |= USB_CDC_SERIAL_STATE_DSR; - } - if ((GPIOB->IDR & GPIO_IDR_IDR15) == 0) { - state |= USB_CDC_SERIAL_STATE_DCD; - } - break; - case 1: - if ((GPIOB->IDR & GPIO_IDR_IDR4) == 0) { - state |= USB_CDC_SERIAL_STATE_DSR; - } - if ((GPIOB->IDR & GPIO_IDR_IDR8) == 0) { - state |= USB_CDC_SERIAL_STATE_DCD; - } - break; - case 2: - if ((GPIOB->IDR & GPIO_IDR_IDR6) == 0) { - state |= USB_CDC_SERIAL_STATE_DSR; - } - if ((GPIOB->IDR & GPIO_IDR_IDR9) == 0) { - state |= USB_CDC_SERIAL_STATE_DCD; - } - break; - default: - break; + if (gpio_pin_get(&port_config->pins[cdc_pin_dsr])) { + state |= USB_CDC_SERIAL_STATE_DSR; + } + if (gpio_pin_get(&port_config->pins[cdc_pin_dcd])) { + state |= USB_CDC_SERIAL_STATE_DCD; } usb_cdc_notify_port_state_change(port, state); } + if (gpio_pin_get(&device_config->config_pin) != usb_cdc_config_mode) { + if (usb_cdc_config_mode) { + usb_cdc_config_mode_leave(); + } else { + usb_cdc_config_mode_enter(); + } + } } else { - dsr_dcd_polling_timer = dsr_dcd_polling_timer - 1; + ctrl_lines_polling_timer = ctrl_lines_polling_timer - 1; } } } @@ -648,12 +709,16 @@ void usb_cdc_data_endpoint_event_handler(uint8_t ep_num, usb_endpoint_event_t ep circ_buf_t *tx_buf = &cdc_state->tx_buf; size_t tx_space_available = circ_buf_space(tx_buf->head, tx_buf->tail, USB_CDC_BUF_SIZE); size_t rx_bytes_available = usb_bytes_available(ep_num); - /* Do not receive data until line state change is complete */ - if ((tx_space_available < rx_bytes_available) || (cdc_state->line_state_change_pending)) { - cdc_state->usb_rx_pending_ep = ep_num; + if ((port == USB_CDC_CONFIG_PORT) && usb_cdc_config_mode) { + usb_cdc_config_mode_process_tx(port); } else { - usb_circ_buf_read(ep_num, tx_buf, USB_CDC_BUF_SIZE); - usb_cdc_port_start_tx(port); + /* Do not receive data until line state change is complete */ + if ((tx_space_available < rx_bytes_available) || (cdc_state->line_state_change_pending)) { + cdc_state->usb_rx_pending_ep = ep_num; + } else { + usb_circ_buf_read(ep_num, tx_buf, USB_CDC_BUF_SIZE); + usb_cdc_port_start_tx(port); + } } } else if (ep_event == usb_endpoint_event_data_sent) { usb_cdc_port_send_rx_usb(port); @@ -680,9 +745,11 @@ usb_status_t usb_cdc_ctrl_process_request(usb_setup_t *setup, void **payload, * If the TX buffer is not empty, defer setting * line coding until all data are sent over the serial port. */ - if (circ_buf_count(tx_buf->head, tx_buf->tail, USB_CDC_BUF_SIZE) != 0) { - dry_run = 1; - usb_cdc_states[port].line_state_change_pending = 1; + if ((port != USB_CDC_CONFIG_PORT) || !usb_cdc_config_mode) { + if (circ_buf_count(tx_buf->head, tx_buf->tail, USB_CDC_BUF_SIZE) != 0) { + dry_run = 1; + usb_cdc_states[port].line_state_change_pending = 1; + } } return usb_cdc_set_line_coding(port, line_coding, dry_run); } diff --git a/usb_cdc.h b/usb_cdc.h index 8c2c06c..bfb6e37 100644 --- a/usb_cdc.h +++ b/usb_cdc.h @@ -8,6 +8,7 @@ #define USB_CDC_H #include +#include "usb_core.h" /* USB CDC Class Codes */ @@ -165,10 +166,31 @@ void usb_cdc_enable(); void usb_cdc_suspend(); void usb_cdc_frame(); +/* CDC Pins */ + +typedef enum { + cdc_pin_rx, + cdc_pin_tx, + cdc_pin_rts, + cdc_pin_cts, + cdc_pin_dsr, + cdc_pin_dtr, + cdc_pin_dcd, + cdc_pin_unknown, + cdc_pin_last = cdc_pin_unknown, +} __attribute__ ((packed)) cdc_pin_t; + + +/* Configuration Changed Hooks */ + +void usb_cdc_reconfigure_port_pin(int port, cdc_pin_t pin); +void usb_cdc_reconfigure(); + /* CDC Device Definitions */ -#define USB_CDC_NUM_PORTS 3 -#define USB_CDC_BUF_SIZE 0x400 -#define USB_CDC_DSR_DCD_POLLING_INTERVAL 20 /* ms */ +#define USB_CDC_NUM_PORTS 3 +#define USB_CDC_BUF_SIZE 0x400 +#define USB_CDC_CRTL_LINES_POLLING_INTERVAL 20 /* ms */ +#define USB_CDC_CONFIG_PORT 0 #endif /* USB_CDC_H */