/* 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 . * */ /** CuVoodoo USB cable tester firmware * @file * @author King Kévin * @date 2016-2020 */ /* standard libraries */ #include // standard integer types #include // standard utilities #include // string utilities #include // date/time utilities #include // utilities to check chars /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // vector table definition #include // interrupt utilities #include // general purpose input output library #include // real-time control clock library #include // external interrupt utilities #include // real time clock utilities #include // independent watchdog utilities #include // debug utilities #include // design utilities #include // flash utilities #include // access to backup registers /* own libraries */ #include "global.h" // board definitions #include "print.h" // printing utilities #include "usb_cdcacm.h" // USB CDC ACM utilities #include "terminal.h" // handle the terminal interface #include "menu.h" // menu utilities #include "usb_cables.h" // USB cables definition #include "lcd_hd44780.h" // LCD utilities #include "oled_text.h" // OLED utilities to display text #include "usb_fusb302.h" // USB-C controller utilities /* external library */ #include "usb_pd.h" // USB Power Delivery definitions /** watchdog period in ms */ #define WATCHDOG_PERIOD 10000 /** set to 0 if the RTC is reset when the board is powered on, only indicates the uptime * set to 1 if VBAT can keep the RTC running when the board is unpowered, indicating the date and time */ #define RTC_DATE_TIME 0 /** number of RTC ticks per second * @note use integer divider of oscillator to keep second precision */ #define RTC_TICKS_SECOND 10 /** RTC time when device is started */ static time_t time_start = 0; /** @defgroup main_flags flag set in interrupts to be processed in main task * @{ */ volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */ /** @} */ /** activity timeout before switching off (in RTC ticks) */ #define SHUTDOWN_TIMEOUT (60 * RTC_TICKS_SECOND) /** if a FUSB302 USB-C controller is present */ static bool present_fusb302 = false; // ==================== // = common functions = // ==================== size_t putc(char c) { size_t length = 0; // number of characters printed static char last_c = 0; // to remember on which character we last sent if ('\n' == c) { // send carriage return (CR) + line feed (LF) newline for each LF if ('\r' != last_c) { // CR has not already been sent usb_cdcacm_putchar('\r'); // send CR over USB length++; // remember we printed 1 character } } usb_cdcacm_putchar(c); // send byte over USB length++; // remember we printed 1 character last_c = c; // remember last character return length; // return number of characters printed } /** switch on power to display */ inline static void display_on(void) { gpio_clear(GPIO_PORT(DISPLAY_POWER_PIN), GPIO_PIN(DISPLAY_POWER_PIN)); } /** switch off power to display */ inline static void display_off(void) { gpio_set(GPIO_PORT(DISPLAY_POWER_PIN), GPIO_PIN(DISPLAY_POWER_PIN)); } /** go into standby mode */ static void standby(void) { while (true) { // try until success SCB_SCR |= SCB_SCR_SLEEPDEEP; // Cortex-M3 standby setting pwr_set_standby_mode(); // power setting pwr_clear_wakeup_flag(); // clear wake-up flag to be able to sleep __WFI(); // go to standby (e.g. shut down) } } /** meaning of the print connection information */ static const char* connection_legend = "connection details: D = pin driven, P = pin pulled, F = pin floating, >/< direction\n"; /** print the pin connection details * @param[in] connection connection details to print */ static void print_connection(const struct usb_connection_t* connection) { if (NULL == connection) { return; } // count number of connections_nb to print between the connections uint8_t connections_nb = 0; if (connection->tx_pull_float) { connections_nb++; } if (connection->tx_drive_float) { connections_nb++; } if (connection->tx_drive_pull) { connections_nb++; } if (connection->rx_pull_float) { connections_nb++; } if (connection->rx_drive_float) { connections_nb++; } if (connection->rx_drive_pull) { connections_nb++; } // print connections if (0 == connections_nb) { puts("no connections"); return; } else { connections_nb--; // one less separator } if (connection->tx_pull_float) { puts("P>F"); if (connections_nb--) { puts(", "); } } if (connection->tx_drive_float) { puts("D>F"); if (connections_nb--) { puts(", "); } } if (connection->tx_drive_pull) { puts("D>P"); if (connections_nb--) { puts(", "); } } if (connection->rx_pull_float) { puts("Frx_drive_float) { puts("Frx_drive_pull) { puts("Pconnections_nb = 0; if (cable->connections) { free(cable->connections); cable->connections = NULL; } cable->connectors_nb = 0; for (uint8_t i = 0; i < LENGTH(cable->connectors); i++) { cable->connectors[i] = false; } cable->load = false; cable->cables_nb = 0; for (uint8_t i = 0; i < LENGTH(cable->cables); i++) { cable->cables[i] = false; } for (uint8_t i = 0; i < LENGTH(cable->unconnected_nb); i++) { cable->unconnected_nb[i] = 0; } for (uint8_t i = 0; i < LENGTH(cable->unspecified_nb); i++) { cable->unspecified_nb[i] = 0; } for (uint8_t i = 0; i < LENGTH(cable->optional_nb); i++) { cable->optional_nb[i] = 0; } cable->cable_best = 0xff; if (cable->unconnected) { free(cable->unconnected); cable->unconnected = NULL; } if (cable->unspecified) { free(cable->unspecified); cable->unspecified = NULL; } if (cable->optional) { free(cable->optional); cable->optional = NULL; } } /** detect cable presence by testing inter-connector ground connections * @param[out] cable what cable it found * @note only sets the ground connections and connections_nb */ static void cable_detect(struct cable_t* cable) { // check input arguments if (NULL == cable) { return; } usb_cables_pins_float(); // start with all pins in safe floating state if (cable->connections) { free(cable->connections); } cable->connections = (uint8_t (*)[2])usb_cables_test_connections(usb_connectors, LENGTH(usb_connectors), false, true, &cable->connections_nb); // figure out which connectors are connector by testing ground pin connections } /** find which connectors the connections belong to * @param[in,out] cable cable for which to find which connectors it connects * @note only updates connectors_nb and connectors based on connections */ static void cable_connectors(struct cable_t* cable) { // check input arguments if (NULL == cable) { return; } // initialize relevant structure variables cable->connectors_nb = 0; for (uint8_t i = 0; i < LENGTH(cable->connectors); i++) { cable->connectors[i] = false; } // ensure connections are available if (NULL == cable->connections || 0 == cable->connections_nb) { return; } // find which connectors the connections belong to for (uint16_t connection = 0; connection < cable->connections_nb; connection++) { const struct usb_connector_t* connector_from = usb_cables_get_connector(cable->connections[connection][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable->connections[connection][1]); if (NULL == connector_from || NULL == connector_to) { continue; } for (uint8_t i = 0; i < LENGTH(cable->connectors) && i < LENGTH(usb_connectors); i++) { if (usb_connectors[i] == connector_from || usb_connectors[i] == connector_to) { cable->connectors[i] = true; } } } // calculate the numbers of connectors connected for (uint8_t i = 0; i < LENGTH(cable->connectors) && i < LENGTH(usb_connectors); i++) { if (cable->connectors[i]) { cable->connectors_nb++; } } } /** find if there is a load on any of the connectors of the cable * @param[in,out] cable cable for which to if there is a load * @note only updates load based on connectors */ static void cable_load(struct cable_t* cable) { // check input arguments if (NULL == cable) { return; } // initialize relevant structure variables cable->load = false; // ensure connections are available if (0 == cable->connectors_nb) { return; } // test of there is a load on any of the connectors of the cable for (uint8_t i = 0; i < LENGTH(cable->connectors) && i < LENGTH(usb_connectors) && !cable->load; i++) { if (!cable->connectors[i]) { continue; } const struct usb_connector_t* connector = usb_connectors[i]; bool load = usb_cables_test_load(connector); if (load) { cable->load = true; } } } /** find which cables match the connector set * @param[in,out] cable cable for which to find matching cable definitions * @note only updates cables_nb and cables based on connectors */ static void cable_cables(struct cable_t* cable) { // check input arguments if (NULL == cable) { return; } // initialize relevant structure variables cable->cables_nb = 0; for (uint8_t i = 0; i < LENGTH(cable->cables); i++) { cable->cables[i] = false; } // ensure connections are available if (0 == cable->connectors_nb) { return; } // find cable with matching connector set for (uint8_t cable_i = 0; cable_i < LENGTH(cable->cables) && cable_i < LENGTH(usb_cables); cable_i++) { cable->cables[cable_i] = false; // start with not matching, and test if it matches // ensure we have the same number of connections as the cable if (usb_cables[cable_i].connectors_nb != cable->connectors_nb) { continue; } // ensure all the connectors we have are also in the cable bool match = true; for (uint8_t i = 0; i < LENGTH(usb_connectors) && match; i++) { if (!cable->connectors[i]) { continue; } bool found = false; for (uint8_t j = 0; j < usb_cables[cable_i].connectors_nb && !found; j++) { if (usb_connectors[i] == usb_cables[cable_i].connectors[j]) { found = true; } } if (!found) { match = false; } } // ensure we also have all the connectors which are in the cable for (uint8_t i = 0; i < usb_cables[cable_i].connectors_nb && match; i++) { bool found = false; for (uint8_t j = 0; j < LENGTH(usb_connectors) && !found; j++) { if (!cable->connectors[j]) { continue; } if (usb_connectors[j] == usb_cables[cable_i].connectors[i]) { found = true; } } if (!found) { match = false; } } cable->cables[cable_i] = match; if (match) { cable->cables_nb++; } } } /** calculate number of issues for matching cables * @param[in,out] cable cable for which to find the number of issues * @note resets connections and connections_nb, sets unconnected_nb, unspecified_nb, optional_nb, and cable_best based on cables */ static void cable_issues_nb(struct cable_t* cable) { // check input arguments if (NULL == cable) { return; } // initialize relevant structure variables for (uint8_t i = 0; i < LENGTH(cable->unconnected_nb); i++) { cable->unconnected_nb[i] = 0; } for (uint8_t i = 0; i < LENGTH(cable->unspecified_nb); i++) { cable->unspecified_nb[i] = 0; } for (uint8_t i = 0; i < LENGTH(cable->optional_nb); i++) { cable->optional_nb[i] = 0; } cable->cable_best = 0xff; // ensure connections are available if (0 == cable->cables_nb) { return; } // get the connectors const struct usb_connector_t* connectors[LENGTH(usb_connectors)]; uint8_t connectors_nb = 0; for (uint8_t i = 0; i < LENGTH(usb_connectors) && i < LENGTH(connectors) && i < LENGTH(cable->connectors) && connectors_nb < cable->connectors_nb; i++) { if (cable->connectors[i]) { connectors[connectors_nb++] = usb_connectors[i]; } } // get all connections once if (cable->connections) { free(cable->connections); cable->connections = NULL; } cable->connections = (uint8_t (*)[2])usb_cables_test_connections(connectors, connectors_nb, true, false, &cable->connections_nb); // calculate score for cables uint16_t best_score = UINT16_MAX; // best cable score for (uint8_t cable_i = 0; cable_i < LENGTH(usb_cables) && cable_i < LENGTH(cable->cables) && cable_i < LENGTH(cable->unconnected_nb) && cable_i < LENGTH(cable->unspecified_nb) && cable_i < LENGTH(cable->optional_nb); cable_i++) { if (!cable->cables[cable_i]) { // skip if the cable connectors do not match continue; } const struct usb_cable_t* usb_cable = &usb_cables[cable_i]; cable->unconnected_nb[cable_i] = usb_cable->mandatory_pairs_nb; cable->unspecified_nb[cable_i] = 0; for (uint16_t i = 0; i < cable->connections_nb; i++) { bool mandatory = false; for (uint8_t j = 0; j < usb_cable->mandatory_pairs_nb; j++) { if (cable->connections[i][0] == usb_cable->mandatory_pairs[j][0] && cable->connections[i][1] == usb_cable->mandatory_pairs[j][1]) { mandatory = true; } else if (cable->connections[i][0] == usb_cable->mandatory_pairs[j][1] && cable->connections[i][1] == usb_cable->mandatory_pairs[j][0]) { mandatory = true; } } bool optional = false; for (uint8_t j = 0; j < usb_cable->optional_pairs_nb && !mandatory; j++) { if (cable->connections[i][0] == usb_cable->optional_pairs[j][0] && cable->connections[i][1] == usb_cable->optional_pairs[j][1]) { optional = true; } else if (cable->connections[i][0] == usb_cable->optional_pairs[j][1] && cable->connections[i][1] == usb_cable->optional_pairs[j][0]) { optional = true; } } if (mandatory) { cable->unconnected_nb[cable_i]--; } else if (optional) { cable->optional_nb[cable_i]++; } else { cable->unspecified_nb[cable_i]++; } } uint16_t score = cable->unconnected_nb[cable_i] + cable->unspecified_nb[cable_i]; if (score < best_score) { best_score = score; cable->cable_best = cable_i; } } } /** list issues for best matching cable * @param[in,out] cable cable for which to calculate the score * @note set unconnected, unspecified, and optional based on cable_best */ static void cable_issues(struct cable_t* cable) { // check input arguments if (NULL == cable) { return; } if (cable->cable_best >= LENGTH(usb_cables) || cable->cable_best >= LENGTH(cable->cables) || !cable->cables[cable->cable_best] || cable->cable_best >= LENGTH(cable->unconnected_nb) || cable->cable_best >= LENGTH(cable->unspecified_nb)) { return; } // initialize relevant variables if (cable->unconnected) { free(cable->unconnected); cable->unconnected = NULL; } cable->unconnected_nb[cable->cable_best] = 0; if (cable->unspecified) { free(cable->unspecified); cable->unspecified = NULL; } cable->unspecified_nb[cable->cable_best] = 0; if (cable->optional) { free(cable->optional); cable->optional = NULL; } cable->optional_nb[cable->cable_best] = 0; // find if cable pairs are actual connection const struct usb_cable_t* usb_cable = &usb_cables[cable->cable_best]; for (uint16_t i = 0; i < usb_cable->mandatory_pairs_nb; i++) { bool match = false; for (uint8_t j = 0; j < cable->connections_nb; j++) { if (cable->connections[j][0] == usb_cable->mandatory_pairs[i][0] && cable->connections[j][1] == usb_cable->mandatory_pairs[i][1]) { match = true; } else if (cable->connections[j][0] == usb_cable->mandatory_pairs[i][1] && cable->connections[j][1] == usb_cable->mandatory_pairs[i][0]) { match = true; } } if (!match) { cable->unconnected_nb[cable->cable_best]++; uint8_t (*new_connections)[2] = realloc(cable->unconnected, cable->unconnected_nb[cable->cable_best] * sizeof(uint8_t[2])); // no integer overflow is possible because of the max number of connections if (NULL == new_connections) { // allocation failed if (cable->unconnected) { free(cable->unconnected); } cable->unconnected = NULL; cable->unconnected_nb[cable->cable_best] = 0; return; // fail-safe return (without indicating error) } cable->unconnected = new_connections; cable->unconnected[cable->unconnected_nb[cable->cable_best] - 1][0] = usb_cable->mandatory_pairs[i][0]; cable->unconnected[cable->unconnected_nb[cable->cable_best] - 1][1] = usb_cable->mandatory_pairs[i][1]; } } // find if connection is defined in cable for (uint16_t i = 0; i < cable->connections_nb; i++) { bool mandatory = false; for (uint8_t j = 0; j < usb_cable->mandatory_pairs_nb; j++) { if (cable->connections[i][0] == usb_cable->mandatory_pairs[j][0] && cable->connections[i][1] == usb_cable->mandatory_pairs[j][1]) { mandatory = true; } else if (cable->connections[i][0] == usb_cable->mandatory_pairs[j][1] && cable->connections[i][1] == usb_cable->mandatory_pairs[j][0]) { mandatory = true; } } bool optional = false; if (!mandatory) { for (uint8_t j = 0; j < usb_cable->optional_pairs_nb; j++) { if (cable->connections[i][0] == usb_cable->optional_pairs[j][0] && cable->connections[i][1] == usb_cable->optional_pairs[j][1]) { optional = true; } else if (cable->connections[i][0] == usb_cable->optional_pairs[j][1] && cable->connections[i][1] == usb_cable->optional_pairs[j][0]) { optional = true; } } } if (optional) { cable->optional_nb[cable->cable_best]++; uint8_t (*new_connections)[2] = realloc(cable->optional, cable->optional_nb[cable->cable_best] * sizeof(uint8_t[2])); // no integer overflow is possible because of the max number of connections if (NULL == new_connections) { // allocation failed if (cable->optional) { free(cable->optional); } cable->optional = NULL; cable->optional_nb[cable->cable_best] = 0; return; // fail-safe return (without indicating error) } cable->optional = new_connections; cable->optional[cable->optional_nb[cable->cable_best] - 1][0] = cable->connections[i][0]; cable->optional[cable->optional_nb[cable->cable_best] - 1][1] = cable->connections[i][1]; } else if (!mandatory) { cable->unspecified_nb[cable->cable_best]++; uint8_t (*new_connections)[2] = realloc(cable->unspecified, cable->unspecified_nb[cable->cable_best] * sizeof(uint8_t[2])); // no integer overflow is possible because of the max number of connections if (NULL == new_connections) { // allocation failed if (cable->unspecified) { free(cable->unspecified); } cable->unspecified = NULL; cable->unspecified_nb[cable->cable_best] = 0; return; // fail-safe return (without indicating error) } cable->unspecified = new_connections; cable->unspecified[cable->unspecified_nb[cable->cable_best] - 1][0] = cable->connections[i][0]; cable->unspecified[cable->unspecified_nb[cable->cable_best] - 1][1] = cable->connections[i][1]; } } } /** print cable connections * @param[in] cable cable for which to calculate the score */ static void cable_print_connections(const struct cable_t* cable) { if (NULL == cable) { return; } if (cable->cable_best < LENGTH(usb_cables) && cable->cable_best < LENGTH(cable->unconnected_nb) && cable->cable_best < LENGTH(cable->unconnected_nb) && cable->cables[cable->cable_best]) { // there is a best cable } else { return; } if (cable->unconnected_nb[cable->cable_best] > 0) { printf("unconnected pins:\n"); for (uint16_t i = 0; i < cable->unconnected_nb[cable->cable_best]; i++) { const struct usb_connector_t* connector_from = usb_cables_get_connector(cable->unconnected[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable->unconnected[i][1]); if (NULL == connector_from || NULL == connector_to) { continue; } printf("- %s ", connector_from->name); if (connector_from->variant) { printf("(%s) ", connector_from->variant); } printf("%s to %s ", usb_pins[cable->unconnected[i][0]].name, connector_to->name); if (connector_to->variant) { printf("(%s) ", connector_to->variant); } printf("%s\n", usb_pins[cable->unconnected[i][1]].name); } } if (cable->unspecified_nb[cable->cable_best] > 0) { printf("unspecified connections:\n"); for (uint16_t i = 0; i < cable->unspecified_nb[cable->cable_best]; i++) { const struct usb_connector_t* connector_from = usb_cables_get_connector(cable->unspecified[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable->unspecified[i][1]); if (NULL == connector_from || NULL == connector_to) { continue; } printf("- %s ", connector_from->name); if (connector_from->variant) { printf("(%s) ", connector_from->variant); } printf("%s to %s ", usb_pins[cable->unspecified[i][0]].name, connector_to->name); if (connector_to->variant) { printf("(%s) ", connector_to->variant); } printf("%s\n", usb_pins[cable->unspecified[i][1]].name); } } if (cable->optional_nb[cable->cable_best] > 0) { printf("optional connections:\n"); for (uint16_t i = 0; i < cable->optional_nb[cable->cable_best]; i++) { const struct usb_connector_t* connector_from = usb_cables_get_connector(cable->optional[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable->optional[i][1]); if (NULL == connector_from || NULL == connector_to) { continue; } printf("- %s ", connector_from->name); if (connector_from->variant) { printf("(%s) ", connector_from->variant); } printf("%s to %s ", usb_pins[cable->optional[i][0]].name, connector_to->name); if (connector_to->variant) { printf("(%s) ", connector_to->variant); } printf("%s\n", usb_pins[cable->optional[i][1]].name); } } } // ================================ // = generic commands definitions = // ================================ /** display available commands * @param[in] argument no argument required */ static void command_help(void* argument); /** show software and hardware version * @param[in] argument no argument required */ static void command_version(void* argument); /** show uptime * @param[in] argument no argument required */ static void command_uptime(void* argument); #if RTC_DATE_TIME /** show date and time * @param[in] argument date and time to set */ static void command_datetime(void* argument); #endif /** reset board * @param[in] argument no argument required */ static void command_reset(void* argument); /** switch to DFU bootloader * @param[in] argument no argument required */ static void command_bootloader(void* argument); // =================== // = custom commands = // =================== /** test USB cables * @param[in] argument no argument required */ static void command_cables(void* argument) { // get cable number uint8_t cable_i = 0xff; if (argument) { cable_i = *(uint32_t*)argument; if (cable_i >= LENGTH(usb_cables)) { printf("cable number %u out of range 0-%u\n", cable_i, LENGTH(usb_cables) - 1); return; } } (void)argument; // we won't use the argument usb_cables_pins_float(); // start with all pins in safe floating state // step 2: check for known cable configuration struct cable_t* cable = calloc(1, sizeof(struct cable_t)); // structure to store cable information printf("= cable check =\n"); for (uint8_t cable_j = 0; cable_j < LENGTH(usb_cables); cable_j++) { // test every cable iwdg_reset(); // kick the dog if (0xff == cable_i || cable_j == cable_i) { // only test specified cable (or all if unspecified) // define the cable to be tested cable_clear(cable); cable->cables_nb = 1; cable->cables[cable_j] = true; cable->connectors_nb = 0; for (uint8_t connector_i = 0; connector_i < usb_cables[cable_j].connectors_nb; connector_i++) { for (uint8_t connector_j = 0; connector_j < LENGTH(usb_connectors) && connector_j < LENGTH(cable->connectors); connector_j++) { if (usb_cables[cable_j].connectors[connector_i] == usb_connectors[connector_j]) { cable->connectors[connector_j] = true; cable->connectors_nb++; break; } } } // show the number of issues cable_issues_nb(cable); uint16_t issues_nb = cable->unconnected_nb[cable_j] + cable->unspecified_nb[cable_j]; printf("%02u %s: %s ", cable_j, 0 == issues_nb ? "pass" : "fail", usb_cables[cable_j].name); if (usb_cables[cable_j].variant) { printf("- %s ", usb_cables[cable_j].variant); } printf("(unconnected=%u/%u, optional=%u/%u, unspecified=%u)\n", cable->unconnected_nb[cable_j], usb_cables[cable_j].mandatory_pairs_nb, cable->optional_nb[cable_j], usb_cables[cable_j].optional_pairs_nb, cable->unspecified_nb[cable_j]); if (cable_j == cable_i) { // print connection details if a specific cable has been provided cable_issues(cable); // get the connections cable_print_connections(cable); // print connection details cable_load(cable); // check if there is a load printf("there is %s load in the cable\n", cable->load ? "a" : "no"); } cable_clear(cable); // free memory } } if (cable) { free(cable); cable = NULL; } usb_cables_pins_float(); // put all pins back in safe floating state } /** find out which USB cable is connected * @param[in] argument no argument required */ static void command_find(void* argument) { (void)argument; // we won't use the argument printf("= finding cable =\n"); bool ground_connectors[LENGTH(usb_connectors)]; bool ground_connected = usb_cables_test_ground(usb_connectors, LENGTH(usb_connectors), ground_connectors); if (ground_connected) { puts("connectors connected by ground:\n"); for (uint8_t i = 0; i < LENGTH(ground_connectors) && i < LENGTH(usb_connectors); i++) { if (!ground_connectors[i]) { continue; } const struct usb_connector_t* connector = usb_connectors[i]; printf("- %s", connector->name); if (connector->variant) { printf(" (%s)", connector->variant); } putc('\n'); } } else { puts("no ground connection found between connectors\n"); } struct cable_t* cable = calloc(1, sizeof(struct cable_t)); // structure to store cable information if (NULL == cable) { // not enough memory for allocation return; } cable_clear(cable); // initialize rest of cable structure // find if cable is connected cable_detect(cable); if (NULL == cable->connections) { if (cable->connections_nb) { printf("no memory available\n"); } goto end; } // find connected connectors cable_connectors(cable); puts("connectors connected by any signal:\n"); for (uint8_t i = 0; i < LENGTH(cable->connectors) && i < LENGTH(usb_connectors); i++) { if (cable->connectors[i]) { printf("- %s", usb_connectors[i]->name); if (usb_connectors[i]->variant) { printf(" (%s)", usb_connectors[i]->variant); } putc('\n'); } } // find cable with matching connector set cable_cables(cable); if (0 == cable->cables_nb) { printf("found no cable with matching connector set\n"); goto end; } printf("found %u cable(s) with matching connectors:\n", cable->cables_nb); for (uint8_t cable_i = 0; cable_i < LENGTH(cable->cables) && cable_i < LENGTH(usb_cables); cable_i++) { if (!cable->cables[cable_i]) { // skip if the cable connectors do not match continue; } printf("- %02u %s\n", cable_i, usb_cables[cable_i].name); } // check if there is a load cable_load(cable); // calculate score for cables cable_issues_nb(cable); printf("cable connection issue(s):\n"); for (uint8_t cable_i = 0; cable_i < LENGTH(usb_cables) && cable_i < LENGTH(cable->cables) && cable_i < LENGTH(cable->unconnected_nb) && cable_i < LENGTH(cable->unspecified_nb); cable_i++) { if (!cable->cables[cable_i]) { // skip if the cable connectors do not match continue; } uint16_t issues = cable->unconnected_nb[cable_i] + cable->unspecified_nb[cable_i]; printf("- %02u %s: %u (unconnected=%u/%u, optional=%u/%u, undefined=%u)\n", cable_i, usb_cables[cable_i].name, issues, cable->unconnected_nb[cable_i], usb_cables[cable_i].mandatory_pairs_nb, cable->optional_nb[cable_i], usb_cables[cable_i].optional_pairs_nb, cable->unspecified_nb[cable_i]); } // print connection details cable_issues(cable); if (cable->cable_best < LENGTH(usb_cables) && cable->cable_best < LENGTH(cable->unconnected_nb) && cable->cable_best < LENGTH(cable->unconnected_nb) && cable->cables[cable->cable_best]) { // there is a matching cable } else { printf("no matching cable found\n"); goto end; } const struct usb_cable_t* usb_cable = &usb_cables[cable->cable_best]; const uint16_t issues = cable->unconnected_nb[cable->cable_best] + cable->unspecified_nb[cable->cable_best]; if (0 == issues) { printf("perfect matching cable: %s", usb_cable->name); if (usb_cable->variant) { printf(" (%s)", usb_cable->variant); } putc('\n'); } else { printf("closest matching cable: %s", usb_cable->name); if (usb_cable->variant) { printf(" (%s)", usb_cable->variant); } putc('\n'); printf("connection issue(s): %u (%u unconnected, %u unspecified)\n", issues, cable->unconnected_nb[cable->cable_best], cable->unspecified_nb[cable->cable_best]); } cable_print_connections(cable); // print cable connections details printf("there is %s load in the cable\n", cable->load ? "a" : "no"); end: usb_cables_pins_float(); // put all pins back in safe floating state if (cable) { cable_clear(cable); // free allocated sub-memory free(cable); // free allocated memory cable = NULL; } } /** set or show pin value * @param[in] argument pin number and level */ static void command_pin(void* argument) { char* pin_str = NULL; // to parse the pin number char* pin_level = NULL; // to parse the pin level const char* delimiter = " "; // words are separated by spaces uint8_t pin_nb = 0; // parsed pin number if (argument) { // pin number and level might have been provided pin_str = strtok((char*)argument, delimiter); // get pin number string if (pin_str) { pin_nb = strtoul(pin_str, NULL, 10); // parse pin number pin_level = strtok(NULL, delimiter); // get pin level } } if (pin_str && pin_nb >= LENGTH(usb_pins)) { printf("pin %u out of range 0-%u\n", pin_nb, LENGTH(usb_pins) - 1); return; } // set pin if (pin_str && pin_level) { const struct usb_pin_t* usb_pin = &usb_pins[pin_nb]; switch (pin_level[0]) { case 'h': gpio_set(usb_pin->port, usb_pin->pin); gpio_set_mode(usb_pin->port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, usb_pin->pin); break; case 'H': gpio_set(usb_pin->port, usb_pin->pin); gpio_set_mode(usb_pin->port, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, usb_pin->pin); break; case 'l': gpio_clear(usb_pin->port, usb_pin->pin); gpio_set_mode(usb_pin->port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, usb_pin->pin); break; case 'L': gpio_clear(usb_pin->port, usb_pin->pin); gpio_set_mode(usb_pin->port, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, usb_pin->pin); break; case 'x': default: gpio_set_mode(usb_pin->port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, usb_pin->pin); } } // print pin level printf("pin state (H: out high, L: out low, h in high, l in low, x in floating) and actual level\n"); // output meaning uint8_t pin_i = 0; // current pin for (uint8_t connector = 0; connector < LENGTH(usb_connectors); connector++) { // test every connector bool connector_print = (!pin_str || (pin_str && pin_nb >= pin_i && pin_nb < pin_i + usb_connectors[connector]->pins_nb)); // if a pin information will be printed for this connector if (connector_print) { printf("%s", usb_connectors[connector]->name); if (usb_connectors[connector]->variant) { printf(" (%s)", usb_connectors[connector]->variant); } printf(":\n"); } for (uint8_t pin = 0; pin < usb_connectors[connector]->pins_nb; pin++) { // test every pin const struct usb_pin_t* usb_pin = &usb_pins[usb_connectors[connector]->pins[pin]]; // get pin if (!pin_str || pin_nb == pin_i) { // show pin state printf("%03u %s: ", pin_i, usb_pin->name); // print USB pin number uint8_t pin_pos = __builtin_ctz(usb_pin->pin); // get the pin number (position of the 1 in the 16-bit) uint8_t offset = (pin_pos < 8) ? (pin_pos * 4) : ((pin_pos - 8) * 4); // get pin offset within port uint8_t mode = (((pin_pos < 8) ? GPIO_CRL(usb_pin->port) : GPIO_CRH(usb_pin->port)) >> (offset + 0)) & 0x3; // get mode from pin for port uint8_t conf = (((pin_pos < 8) ? GPIO_CRL(usb_pin->port) : GPIO_CRH(usb_pin->port)) >> (offset + 2)) & 0x3; // get configuration from pin for port // show set value if (0 == mode) { // pin configured as input if (1 == conf) { // pin is in floating configuration putc('x'); } else if (0 == (GPIO_ODR(usb_pin->port) & usb_pin->pin)) { putc('l'); } else { putc('h'); } } else { // pin configured as output if (0 == (GPIO_ODR(usb_pin->port) & usb_pin->pin)) { putc('L'); } else { putc('H'); } } // show actual value if (gpio_get(usb_pin->port, usb_pin->pin)) { putc(0 == mode ? 'h': 'H'); } else { putc(0 == mode ? 'l': 'L'); } putc('\n'); } pin_i++; // increase global pin number } // pin if (connector_print) { putc('\n'); // separate connectors for readability } } // connector } /** run self test to test board connection to connectors * @param[in] argument no argument required */ static void command_test(void* argument) { (void)argument; // we won't use the argument usb_cables_pins_float(); // start with all pins in safe floating state printf("= test =\n"); printf("run test to check board connections\n"); printf("press any key to interrupt test\n\n"); // ensure all pins are floating printf("remove all cables from connectors\n"); bool float_errors = true; // to test if all pins are floating while (float_errors) { float_errors = false; // restart test for (uint8_t connector = 0; connector < LENGTH(usb_connectors); connector++) { // test every connector for (uint8_t pin = 0; pin < usb_connectors[connector]->pins_nb; pin++) { // test every pin const struct usb_pin_t* usb_pin = &usb_pins[usb_connectors[connector]->pins[pin]]; // get pin gpio_set_mode(usb_pin->port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, usb_pin->pin); // we will test if the input is floating by checking against a pull up and down gpio_set(usb_pin->port, usb_pin->pin); // pull up sleep_us(10); // wait for GPIO/line to settle bool high = (0 != gpio_get(usb_pin->port, usb_pin->pin)); // test if pin is high gpio_clear(usb_pin->port, usb_pin->pin); // pull down sleep_us(10); // wait for GPIO/line to settle bool low = (0 == gpio_get(usb_pin->port, usb_pin->pin)); // test if pin is low gpio_set_mode(usb_pin->port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, usb_pin->pin); // put back to floating if (high && low) { // pull up and down worked } else { // pull up or down did not work printf("%s ", usb_connectors[connector]->name); if (usb_connectors[connector]->variant) { printf("(%s) ", usb_connectors[connector]->variant); } printf("%s is not floating\n", usb_pin->name); // print erroneous pin float_errors = true; // remember there is an error } } // pin } // connector if (float_errors) { if (user_input_available) { // user interruption goto end; } sleep_ms(500); // wait a bit before retesting if (user_input_available) { // user interruption goto end; } } } // float_errors printf("all pins are floating\n\n"); // cables to test const enum usb_cable_e test_cables[] = { USB_CABLE_AB3_HOST, USB_CABLE_AB3_DEVICE, USB_CABLE_AAB2_HOST, USB_CABLE_AUB3_HOST, USB_CABLE_CPINSHUNT_HOST, USB_CABLE_CPINSHUNT_DEVICE, }; for (uint8_t cable_id = 0; cable_id < LENGTH(test_cables); cable_id++) { const struct usb_cable_t* usb_cable = &usb_cables[cable_id]; printf("connect %s cable to connectors:\n", usb_cable->name); for (uint8_t connector = 0; connector < usb_cable->connectors_nb; connector++) { printf("- %s", usb_connectors[connector]->name); if (usb_connectors[connector]->variant) { printf(" (%s)", usb_connectors[connector]->variant); } putc('\n'); } bool cable_ok = false; // if the cable is connected while (!cable_ok) { // wait until all pin pairs of cable are connected uint8_t defined, optional, undefined; // pair counting variables cable_ok = usb_cables_test_cable(usb_cable, &defined, &optional, &undefined, true); // test cable if (!cable_ok && defined > 0) { // not all pairs are connected printf("connection issues: defined=%u/%u, optional=%u/%u, undefined=%u\n", defined, usb_cable->mandatory_pairs_nb, optional, usb_cable->optional_pairs_nb, undefined); // show issue summary } if (!cable_ok) { if (user_input_available) { // user interruption goto end; } sleep_ms(500); // wait a bit before retesting if (user_input_available) { // user interruption goto end; } } } printf("cable connections are OK\n\n"); } printf("all connectors are OK, the board is fine\n"); end: usb_cables_pins_float(); // put pins back to safe state if (user_input_available) { printf("test interrupted\n"); while (user_input_available) { // test has been interrupted user_input_get(); // discard input } } } /** test connection between pins * @param[in] argument NULL to test all connections, "intra" to test only connection internal connections, "inter" to test only inter-connector connections */ static void command_connections(void* argument) { char* str = (char*)argument; // we won't use the argument bool intra = false; // test only connection internal connections bool inter = false; // test only inter-connector connections if (str) { if (0 == strcmp(str, "intra")) { intra = true; } else if (0 == strcmp(str, "inter")) { inter = true; } else { printf("unknown argument: %s\n", str); return; } } uint16_t connections_nb = 0; uint8_t (*connections)[2] = NULL; if (intra) { printf("= testing internal connections =\n"); puts(connection_legend); for (uint8_t i = 0; i < LENGTH(usb_connectors); i++) { // test pin connections const struct usb_connector_t* connector = usb_connectors[i]; connections = (uint8_t (*)[2])usb_cables_test_connections(&connector, 1, true, false, &connections_nb); if (NULL == connections && connections_nb) { printf("no memory available\n"); } // check if there is a load on the cable bool load = usb_cables_test_load(connector); if (0 == connections_nb && !load) { continue; } printf("%s", connector->name); if (connector->variant) { printf(" (%s)", connector->variant); } printf(": %u connection(s)\n", connections_nb); for (uint16_t connection = 0; connection < connections_nb; connection++) { struct usb_connection_t connection_detail; const bool connected = usb_cables_test_pins(&usb_pins[connections[connection][0]], &usb_pins[connections[connection][1]], &connection_detail); printf("- %s to %s (", usb_pins[connections[connection][0]].name, usb_pins[connections[connection][1]].name); if (connected) { print_connection(&connection_detail); } else { puts("connection dropped"); } puts(")\n"); } printf("there is %s load on the connector\n", load ? "a" : "no"); if (connections) { free(connections); connections = NULL; } } } else { if (inter) { printf("= testing connections between connectors =\n"); } else { printf("= testing all connections =\n"); } puts(connection_legend); connections = (uint8_t (*)[2])usb_cables_test_connections(usb_connectors, LENGTH(usb_connectors), !inter, false, &connections_nb); if (NULL == connections) { if (connections_nb) { printf("no memory available\n"); } else { printf("no connections\n"); } return; } printf("found %u connections:\n", connections_nb); for (uint16_t i = 0; i < connections_nb; i++) { const struct usb_connector_t* connector_from = usb_cables_get_connector(connections[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(connections[i][1]); if (NULL == connector_from || NULL == connector_to) { puts("no connector for a pin pair\n"); continue; } // get connection details struct usb_connection_t connection; const bool connected = usb_cables_test_pins(&usb_pins[connections[i][0]], &usb_pins[connections[i][1]], &connection); printf("%s ", connector_from->name); if (connector_from->variant) { printf("(%s) ", connector_from->variant); } printf("%s to %s ", usb_pins[connections[i][0]].name, connector_to->name); if (connector_to->variant) { printf("(%s) ", connector_to->variant); } printf("%s (", usb_pins[connections[i][1]].name); if (connected) { print_connection(&connection); } else { puts("connection dropped"); } puts(")\n"); } if (connections) { free(connections); connections = NULL; } } } /** print discovery identity response (e.g. eMarker information) * param[in] packet FUSB302 packet containing VDM information */ static void print_emarker(uint8_t* packet) { const uint16_t header = (packet[2] << 8) + packet[1]; if (0 == PD_HEADER_CNT(header)) { // it's a control packet, not a data packet return; } if (PD_DATA_VENDOR_DEF != PD_HEADER_TYPE(header)) { // not a VDM message return; } // get objects uint32_t objects[7]; // maximum message length for (uint8_t i = 0; i < PD_HEADER_CNT(header); i++) { objects[i] = (packet[i * 4 + 3 + 3] << 24) + (packet[i * 4 + 3 + 2] << 16) + (packet[i * 4 + 3 + 1] << 8) + (packet[i * 4 + 3 + 0] << 0); } puts("cable eMarker attributes:\n"); // decode some messages const uint32_t vdm_header = objects[0]; if (vdm_header & VDO_SVDM_TYPE && VDO_CMDT(CMDT_RSP_ACK) == (vdm_header & VDO_CMDT_MASK) && CMD_DISCOVER_IDENT == (vdm_header & 0x1f)) { // we have a discover identity response if (PD_HEADER_CNT(header) > 1) { // ID Header VDO (6.4.4.3.1.1) //printf("- host %s enumerate\n", (objects[1] & (1 << 31)) ? "can" : "can't"); //printf("- device %s be enumerated\n", (objects[1] & (1 << 30)) ? "can" : "can't"); if (PD_HEADER_PROLE(header)) { // this is a plug switch (PD_IDH_PTYPE(objects[1])) { case 3: puts("- cable: passive\n"); break; case 4: puts("- cable: active\n"); break; default: break; } } printf("- VID: %04x\n", PD_IDH_VID(objects[1])); } if (PD_HEADER_CNT(header) > 2) { // Cert Stat VDO (6.4.4.3.1.2) //printf("- cert stat: %08x\n", objects[2]); } if (PD_HEADER_CNT(header) > 3) { // Product VDO (6.4.4.3.1.3) //printf("- product ID: %04x, bcdDevice: %04x\n", objects[3] >> 16, objects[3] & 0xffff); } if (PD_HEADER_CNT(header) > 3 && PD_HEADER_PROLE(header) && 3 == PD_IDH_PTYPE(objects[1])) { // Passive Cable VDO (6.4.4.3.1.4) union cable_vdo cable; memset(&cable, 0, sizeof(cable)); cable.raw_value = objects[4]; printf("- HW version: %u\n", cable.p_rev30.hw_version); printf("- FW version: %u\n", cable.p_rev30.fw_version); printf("- cable length: ~%um\n", ((cable.raw_value >> 13) & 0x7)); printf("- VCONN: %srequired\n", ((cable.raw_value >> 11) & 0x3) ? "" : "not "); printf("- maximum VBUS voltage: %uV\n", ((cable.raw_value >> 9) & 0x3) * 10 + 20); puts("- maximum VBUS current: "); switch (((cable.raw_value >> 5) & 0x3)) { case 1: putc('3'); break; case 2: putc('5'); break; default: putc('?'); break; } puts("A\n"); puts("- SuperSpeed: USB "); switch (cable.p_rev30.ss) { case 0: puts("2.0"); break; case 1: puts("3.2 Gen 1"); break; case 2: puts("3.2 Gen 2"); break; default: puts("???"); break; } putc('\n'); } } } /** test USB-C plug * @param[in] argument no argument required */ static void command_cplug(void* argument) { (void)argument; // we won't use the argument const enum usb_connectors_e connectors[] = {USB_CONNECTOR_C_HOST, USB_CONNECTOR_C_DEVICE}; // USB-C connectors to test puts("= testing C plugs =\n"); puts(connection_legend); usb_cables_pins_float(); // put all pins to float before making checks // test each connector for (uint8_t connector_id = 0; connector_id < LENGTH(connectors); connector_id++) { const struct usb_connector_t* connector = usb_connectors[connectors[connector_id]]; // get connector if (NULL == connector) { continue; } printf("connector: %s", connector->name); if (connector->variant) { printf(" (%s)", connector->variant); } putc('\n'); // check if plug is present in socket by checking if the GND pins are interconnected bool gnd_any = false; bool gnd_all = true; bool gnd; gnd = usb_cables_test_pins(&usb_pins[connector->pins[1]], &usb_pins[connector->pins[12]], NULL); // A1- A12 GND gnd_any |= gnd; gnd_all &= gnd; gnd = usb_cables_test_pins(&usb_pins[connector->pins[1]], &usb_pins[connector->pins[12 + 1]], NULL); // A1- B1 GND gnd_any |= gnd; gnd_all &= gnd; gnd = usb_cables_test_pins(&usb_pins[connector->pins[1]], &usb_pins[connector->pins[12 + 12]], NULL); // A1- B12 GND gnd_any |= gnd; gnd_all &= gnd; gnd = usb_cables_test_pins(&usb_pins[connector->pins[12]], &usb_pins[connector->pins[12 + 1]], NULL); // A12 - B1 GND gnd_any |= gnd; gnd_all &= gnd; gnd = usb_cables_test_pins(&usb_pins[connector->pins[12]], &usb_pins[connector->pins[12 + 12]], NULL); // A12 - B12 GND gnd_any |= gnd; gnd_all &= gnd; gnd = usb_cables_test_pins(&usb_pins[connector->pins[12 + 1]], &usb_pins[connector->pins[12 + 12]], NULL); // B1- B12 GND gnd_any |= gnd; gnd_all &= gnd; printf("- %s GND pins interconnected\n", gnd_all ? "all" : (gnd_any ? "some" : "no")); // check if plug is present in socket by checking if the VBUS pins are interconnected bool vbus_any = false; bool vbus_all = true; bool vbus; vbus = usb_cables_test_pins(&usb_pins[connector->pins[4]], &usb_pins[connector->pins[9]], NULL); // A4- A9 VBUS vbus_any |= vbus; vbus_all &= vbus; vbus = usb_cables_test_pins(&usb_pins[connector->pins[4]], &usb_pins[connector->pins[12 + 4]], NULL); // A4- B4 VBUS vbus_any |= vbus; vbus_all &= vbus; vbus = usb_cables_test_pins(&usb_pins[connector->pins[4]], &usb_pins[connector->pins[12 + 9]], NULL); // A4- B9 VBUS vbus_any |= vbus; vbus_all &= vbus; vbus = usb_cables_test_pins(&usb_pins[connector->pins[9]], &usb_pins[connector->pins[12 + 4]], NULL); // A9 - B4 VBUS vbus_any |= vbus; vbus_all &= vbus; vbus = usb_cables_test_pins(&usb_pins[connector->pins[9]], &usb_pins[connector->pins[12 + 9]], NULL); // A9 - B9 VBUS vbus_any |= vbus; vbus_all &= vbus; vbus = usb_cables_test_pins(&usb_pins[connector->pins[12 + 4]], &usb_pins[connector->pins[12 + 9]], NULL); // B4- B9 VBUS vbus_any |= vbus; vbus_all &= vbus; printf("- %s VBUS pins interconnected\n", vbus_all ? "all" : (vbus_any ? "some" : "no")); if (gnd_all && vbus_all) { puts("> plug present\n"); } else if (gnd_all || vbus_all || gnd_any || vbus_any) { puts("> faulty plug present\n"); } else { puts("> no plug present\n"); } // check if it is a powered cable (VCONN is connected to ground by Ra), or connected to sink struct usb_connection_t cc1_connection; bool cc1 = usb_cables_test_pins(&usb_pins[connector->pins[5]], &usb_pins[connector->pins[12 + 1]], &cc1_connection); // A5 CC1 - A1 GND printf("- CC1 %sconnected to GND (", cc1 ? "" : "not "); print_connection(&cc1_connection); puts(")\n"); struct usb_connection_t cc2_connection; bool cc2 = usb_cables_test_pins(&usb_pins[connector->pins[12 + 5]], &usb_pins[connector->pins[12 + 1]], &cc2_connection); // B5 CC1 - A1 GND printf("- CC2 %sconnected to GND (", cc2 ? "" : "not "); print_connection(&cc2_connection); puts(")\n"); if (gnd_all || vbus_all || gnd_any || vbus_any) { if (cc1_connection.tx_drive_pull && cc1_connection.rx_pull_float && cc2_connection.tx_drive_pull && cc2_connection.rx_pull_float) { puts("> powered cable to be connected to a sink on the other end (B, mini-B, or micro-B plug)\n"); } else if ((cc1_connection.tx_drive_pull && cc1_connection.rx_pull_float) || (cc2_connection.tx_drive_pull && cc2_connection.rx_pull_float)) { puts("> powered cable, or to be connected to a sink on the other end (B, mini-B, or micro-B plug)\n"); } else { puts("> unpowered cable\n"); } } // check if it should be connected to a source cc1 = usb_cables_test_pins(&usb_pins[connector->pins[5]], &usb_pins[connector->pins[4]], &cc1_connection); // A5 CC1 - A4 VBUS printf("- CC1 %sconnected to VBUS (", cc1 ? "" : "not "); print_connection(&cc1_connection); puts(")\n"); cc2 = usb_cables_test_pins(&usb_pins[connector->pins[12 + 5]], &usb_pins[connector->pins[4]], &cc2_connection); // B5 CC1 - A1 VBUS printf("- CC2 %sconnected to VBUS (", cc2 ? "" : "not "); print_connection(&cc2_connection); puts(")\n"); if (gnd_all || vbus_all || gnd_any || vbus_any) { if (cc1 || cc2) { puts("> to be connected to a source on the other end (A plug)\n"); } else { puts("> not to be connected to a source on the other end (A plug)\n"); } } // find out if a resistor is present using USB-C controller if (present_fusb302 && USB_CONNECTOR_C_DEVICE == connectors[connector_id]) { // HW v1 (modified for a v2 prototype) has only a FUSB302 on the C device port // ground pins need to be pulled low for the resistor test to work usb_cables_pins_float(); // start with all floating to remove interferences for (uint8_t i = 0; i < connector->pins_nb; i++) { if (connector->pins[i] >= LENGTH(usb_pins)) { continue; } const struct usb_pin_t pin = usb_pins[connector->pins[i]]; if (USB_PIN_TYPE_GROUND != pin.type) { continue; } gpio_clear(pin.port, pin.pin); gpio_set_mode(pin.port, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, pin.pin); } // figure out resistors on CC lines uint8_t cc_pd = 0; // line used for PD communication for (uint8_t cc = 1; cc < 3; cc++) { const char* r_values[] = {"short to ground", "Ra", "Rd", "Rp", "open"}; int16_t r = usb_fusb302_r(cc); printf("- CC%u resistor: ", cc); if (r < 0 || r >= (int16_t)LENGTH(r_values)) { printf("error=%d", r); } else { puts(r_values[r]); } putc('\n'); if (1 == r) { printf("> powered cable (CC%u is CC, CC%u is VCONN)\n", (1 == cc) ? 2 : 1, cc); cc_pd = ((1 == cc) ? 2 : 1); // remember this is the CC line to communicate with eMarker } else if (2 == r) { puts("> to be connected to a sink on the other end (B, mini-B, or micro-B plug)\n"); } else if (3 == r) { puts("> to be connected to a source on the other end (A plug)\n"); } } if (cc_pd) { // read cable eMarker uint8_t stage = 0; int16_t rc = usb_fusb302_pd(cc_pd); // configure CC channel for PD communication (also flushes data) if (rc < 0) { stage = 1; goto pd_end; } rc = usb_fusb302_discover_identity_request(); // send discover identity request if (rc < 0) { stage = 2; goto pd_end; } sleep_ms(2); // wait tReceive = 1.1 ms for goodCRC response (PD3.0 section 6.6.1) bool expected_message = false; uint8_t packet[35]; while (!expected_message) { // wait got goodCRC // I don't know why, after the first this action is called, the TX packet is also in the RX FIFO rc = usb_fusb302_packet_read(packet); if (0 == rc) { // RX FIFO is empty stage = 3; goto pd_end; } else if (rc < 0) { stage = 4; goto pd_end; } if (6 != ((packet[0] >> 5) & 0x7)) { // not a SOP' response continue; } uint16_t header = *(uint16_t*)(&packet[1]); if (0 != PD_HEADER_CNT(header)) { // not a control message continue; } if (PD_CTRL_GOOD_CRC != PD_HEADER_TYPE(header)) { // not a goodCRC message continue; } expected_message = true; } if (!usb_fusb302_packet_checksum(packet)) { // verify CRC stage = 5; goto pd_end; } sleep_ms(15); // wait tReceiverResponse = 15 ms for request response (PD3.0 section 6.6.2) expected_message = false; while (!expected_message) { // wait got identity response // I don't know why, after the first this action is called, the TX packet is also in the RX FIFO rc = usb_fusb302_packet_read(packet); if (0 == rc) { // RX FIFO is empty stage = 6; goto pd_end; } else if (rc < 0) { stage = 7; goto pd_end; } if (6 != ((packet[0] >> 5) & 0x7)) { // not a SOP' response continue; } uint16_t header = *(uint16_t*)(&packet[1]); if (0 == PD_HEADER_CNT(header)) { // not a data message continue; } if (PD_DATA_VENDOR_DEF != PD_HEADER_TYPE(header)) { // not a VDM message continue; } expected_message = true; } if (!usb_fusb302_packet_checksum(packet)) { // verify CRC stage = 8; goto pd_end; } pd_end: if (0 == stage) { print_emarker(packet); sleep_ms(2); // wait a bit for the goodCRC to be received by the eMarker } else { //puts("could not read eMarker\n"); printf("could not read eMarker (stage=%u, error=%d)\n", stage, rc); } usb_fusb302_disconnect(); // remove all CC connections } // put ground back to floating usb_cables_pins_float(); // not the most efficient way, but time is not critical } } // test interconnection const struct usb_connector_t* c1 = usb_connectors[USB_CONNECTOR_C_HOST]; const struct usb_connector_t* c2 = usb_connectors[USB_CONNECTOR_C_DEVICE]; if (NULL == c1 || NULL == c2) { return; } puts("cable interconnection (C host to C device)\n"); struct usb_connection_t connection; bool connected; connected = usb_cables_test_pins(&usb_pins[c1->pins[5]], &usb_pins[c2->pins[5]], &connection); // A5 CC1 - A5 CC1 printf("- CC1 %sconnected to CC1 (", connected ? "" : "not "); print_connection(&connection); puts(")\n"); connected = usb_cables_test_pins(&usb_pins[c1->pins[5]], &usb_pins[c2->pins[12 + 5]], &connection); // A5 CC1 - B5 CC2 printf("- CC1 %sconnected to CC2 (", connected ? "" : "not "); print_connection(&connection); puts(")\n"); connected = usb_cables_test_pins(&usb_pins[c1->pins[12 + 5]], &usb_pins[c2->pins[5]], &connection); // B5 CC2 - A5 CC1 printf("- CC2 %sconnected to CC1 (", connected ? "" : "not "); print_connection(&connection); puts(")\n"); connected = usb_cables_test_pins(&usb_pins[c1->pins[12 + 5]], &usb_pins[c2->pins[12 + 5]], &connection); // B5 CC2 - B5 CC2 printf("- CC2 %sconnected to CC2 (", connected ? "" : "not "); print_connection(&connection); puts(")\n"); } // ==================== // = list of commands = // ==================== /** list of all supported commands */ static const struct menu_command_t menu_commands[] = { { .shortcut = 'h', .name = "help", .command_description = "display help", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_help, }, { .shortcut = 'V', .name = "version", .command_description = "show software and hardware version", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_version, }, { .shortcut = 'U', .name = "uptime", .command_description = "show uptime", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_uptime, }, #if RTC_DATE_TIME { .shortcut = 'D', .name = "date", .command_description = "show/set date and time", .argument = MENU_ARGUMENT_STRING, .argument_description = "[YYYY-MM-DD HH:MM:SS]", .command_handler = &command_datetime, }, #endif { .shortcut = 'R', .name = "reset", .command_description = "reset board", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_reset, }, { .shortcut = 'B', .name = "bootloader", .command_description = "reboot into DFU bootloader", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_bootloader, }, { .shortcut = 'c', .name = "cables", .command_description = "test cable(s)", .argument = MENU_ARGUMENT_UNSIGNED, .argument_description = "[nb]", .command_handler = &command_cables, }, { .shortcut = 'f', .name = "find", .command_description = "find cable", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_find, }, { .shortcut = 'p', .name = "pin", .command_description = "set/show pin level", .argument = MENU_ARGUMENT_STRING, .argument_description = "[nb] [H/L/h/l/x]", .command_handler = &command_pin, }, { .shortcut = 't', .name = "test", .command_description = "run board test", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_test, }, { .shortcut = 'x', .name = "connections", .command_description = "test all pin connections", .argument = MENU_ARGUMENT_STRING, .argument_description = "[inter|intra]", .command_handler = &command_connections, }, { .shortcut = 'C', .name = "cplug", .command_description = "test USB-C plugs", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_cplug, }, }; // ==================== // = generic commands = // ==================== static void command_help(void* argument) { (void)argument; // we won't use the argument printf("available commands:\n"); menu_print_commands(menu_commands, LENGTH(menu_commands)); // print global commands } static void command_version(void* argument) { (void)argument; // we won't use the argument printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date // get device identifier (DEV_ID) // 0x412: low-density, 16-32 kB flash // 0x410: medium-density, 64-128 kB flash // 0x414: high-density, 256-512 kB flash // 0x430: XL-density, 768-1024 kB flash // 0x418: connectivity puts("device family: "); switch (DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK) { case 0: // this is a known issue document in STM32F10xxC/D/E Errata sheet, without workaround puts("unreadable\n"); break; case 0x412: puts("low-density\n"); break; case 0x410: puts("medium-density\n"); break; case 0x414: puts("high-density\n"); break; case 0x430: puts("XL-density\n"); break; case 0x418: puts("connectivity\n"); break; default: puts("unknown\n"); break; } // show flash size puts("flash size: "); if (0xffff == DESIG_FLASH_SIZE) { puts("unknown (probably a defective micro-controller\n"); } else { printf("%u KB\n", DESIG_FLASH_SIZE); } // display device identity printf("device id: %08x%08x%08x\n", DESIG_UNIQUE_ID0, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID2); } static void command_uptime(void* argument) { (void)argument; // we won't use the argument uint32_t uptime = (rtc_get_counter_val() - time_start) / RTC_TICKS_SECOND; // get time from internal RTC printf("uptime: %u.%02u:%02u:%02u\n", uptime / (24 * 60 * 60), (uptime / (60 * 60)) % 24, (uptime / 60) % 60, uptime % 60); } #if RTC_DATE_TIME static void command_datetime(void* argument) { char* datetime = (char*)argument; // argument is optional date time if (NULL == argument) { // no date and time provided, just show the current day and time time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND; // get time from internal RTC struct tm* time_tm = localtime(&time_rtc); // convert time printf("date: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm->tm_year, time_tm->tm_mon, time_tm->tm_mday, time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec); } else { // date and time provided, set it const char* malformed = "date and time malformed, expecting YYYY-MM-DD HH:MM:SS\n"; struct tm time_tm; // to store the parsed date time if (strlen(datetime) != (4 + 1 + 2 + 1 + 2) + 1 + (2 + 1 + 2 + 1 + 2)) { // verify date/time is long enough printf(malformed); return; } if (!(isdigit((int8_t)datetime[0]) && isdigit((int8_t)datetime[1]) && isdigit((int8_t)datetime[2]) && isdigit((int8_t)datetime[3]) && '-' == datetime[4] && isdigit((int8_t)datetime[5]) && isdigit((int8_t)datetime[6]) && '-' == datetime[7] && isdigit((int8_t)datetime[8]) && isdigit((int8_t)datetime[9]) && ' ' == datetime[10] && isdigit((int8_t)datetime[11]) && isdigit((int8_t)datetime[12]) && ':' == datetime[13] && isdigit((int8_t)datetime[14]) && isdigit((int8_t)datetime[15]) && ':' == datetime[16] && isdigit((int8_t)datetime[17]) && isdigit((int8_t)datetime[18]))) { // verify format (good enough to not fail parsing) printf(malformed); return; } time_tm.tm_year = strtol(&datetime[0], NULL, 10) - 1900; // parse year time_tm.tm_mon = strtol(&datetime[5], NULL, 10); // parse month time_tm.tm_mday = strtol(&datetime[8], NULL, 10); // parse day time_tm.tm_hour = strtol(&datetime[11], NULL, 10); // parse hour time_tm.tm_min = strtol(&datetime[14], NULL, 10); // parse minutes time_tm.tm_sec = strtol(&datetime[17], NULL, 10); // parse seconds time_t time_rtc = mktime(&time_tm); // get back seconds time_start = time_rtc * RTC_TICKS_SECOND + (rtc_get_counter_val() - time_start); // update uptime with current date rtc_set_counter_val(time_rtc * RTC_TICKS_SECOND); // save date/time to internal RTC printf("date and time saved: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm.tm_year, time_tm.tm_mon, time_tm.tm_mday, time_tm.tm_hour, time_tm.tm_min, time_tm.tm_sec); } } #endif static void command_reset(void* argument) { (void)argument; // we won't use the argument scb_reset_system(); // reset device while (true); // wait for the reset to happen } static void command_bootloader(void* argument) { (void)argument; // we won't use the argument // set DFU magic to specific RAM location __dfu_magic[0] = 'D'; __dfu_magic[1] = 'F'; __dfu_magic[2] = 'U'; __dfu_magic[3] = '!'; scb_reset_system(); // reset system (core and peripherals) while (true); // wait for the reset to happen } /** process user command * @param[in] str user command string (\0 ended) */ static void process_command(char* str) { // ensure actions are available if (NULL == menu_commands || 0 == LENGTH(menu_commands)) { return; } // don't handle empty lines if (!str || 0 == strlen(str)) { return; } bool command_handled = false; if (!command_handled) { command_handled = menu_handle_command(str, menu_commands, LENGTH(menu_commands)); // try if this is not a global command } if (!command_handled) { printf("command not recognized. enter help to list commands\n"); } } // ======== // = main = // ======== /** program entry point * this is the firmware function started by the micro-controller */ void main(void); void main(void) { rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock #if DEBUG // enable functionalities for easier debug DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted DBGMCU_CR |= DBGMCU_CR_WWDG_STOP; // stop window watchdog counter when code is halted DBGMCU_CR |= DBGMCU_CR_STANDBY; // allow debug also in standby mode (keep digital part and clock powered) DBGMCU_CR |= DBGMCU_CR_STOP; // allow debug also in stop mode (keep clock powered) DBGMCU_CR |= DBGMCU_CR_SLEEP; // allow debug also in sleep mode (keep clock powered) #else RCC_APB1ENR |= RCC_APB1ENR_BKPEN | RCC_APB1ENR_PWREN; // enable access to power register if (RCC_CSR & RCC_CSR_IWDGRSTF && 0x00ff == BKP_DR1) { // we have been woken up by independent watchdog but actually want to stay in standby mode RCC_CSR |= RCC_CSR_RMVF; // clear reset flags // the reset will have clearer the software set watchdog standby(); // go to standby (e.g. shut down) } // setup watchdog to reset in case we get stuck (i.e. when an error occurred) iwdg_set_period_ms(WATCHDOG_PERIOD); // set independent watchdog period iwdg_start(); // start independent watchdog PWR_CR |= PWR_CR_DBP; // disable backup domain write protection BKP_DR1 = 0; // clear backup register to not indicate we want to stay in stand by PWR_CR &= ~PWR_CR_DBP; // enable backup domain write protection #endif board_setup(); // setup board // setup power to display an pull-up D+ to indicate USB connect rcc_periph_clock_enable(GPIO_RCC(DISPLAY_POWER_PIN)); // enable clock for GPIO peripheral gpio_set_mode(GPIO_PORT(DISPLAY_POWER_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(DISPLAY_POWER_PIN)); // set pin to output open-drain since it is controlled by pMOS display_on(); usb_cdcacm_setup(); // setup USB CDC ACM (for printing) puts("\nwelcome to the CuVoodoo USB cable tester\n"); // print welcome message #if DEBUG // show reset cause if (RCC_CSR & (RCC_CSR_LPWRRSTF | RCC_CSR_WWDGRSTF | RCC_CSR_IWDGRSTF | RCC_CSR_SFTRSTF | RCC_CSR_PORRSTF | RCC_CSR_PINRSTF)) { puts("reset cause(s):"); if (RCC_CSR & RCC_CSR_LPWRRSTF) { puts(" low-power"); } if (RCC_CSR & RCC_CSR_WWDGRSTF) { puts(" window-watchdog"); } if (RCC_CSR & RCC_CSR_IWDGRSTF) { puts(" independent-watchdog"); } if (RCC_CSR & RCC_CSR_SFTRSTF) { puts(" software"); } if (RCC_CSR & RCC_CSR_PORRSTF) { puts(" POR/PDR"); } if (RCC_CSR & RCC_CSR_PINRSTF) { puts(" pin"); } putc('\n'); RCC_CSR |= RCC_CSR_RMVF; // clear reset flags } #endif #if !(DEBUG) && false // show watchdog information printf("setup watchdog: %.2fs", WATCHDOG_PERIOD / 1000.0); if (FLASH_OBR & FLASH_OBR_OPTERR) { puts(" (option bytes not set in flash: software wachtdog used, not automatically started at reset)\n"); } else if (FLASH_OBR & FLASH_OBR_WDG_SW) { puts(" (software watchdog used, not automatically started at reset)\n"); } else { puts(" (hardware watchdog used, automatically started at reset)\n"); } #endif // setup RTC // the USB cable tester does not have a dedicated external 32.678 kHz LSE oscillator rtc_auto_awake(RCC_HSE, 8000000 / 128 / RTC_TICKS_SECOND - 1); // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running) rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds" nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt time_start = rtc_get_counter_val(); // get start time from internal RTC // setup LCD display lcd_hd44780_i2c_addr = 0x3f; // set LCD backpack I²C slave address const char* lcd_default_line1 = "USB cable tester"; // default LCD text, when no cable is connected const char* lcd_default_line2 = "plug in cable"; // default LCD text, when no cable is connected if (lcd_hd44780_setup(true, false)) { // setup LCD communication lcd_hd44780_display_control(true, false, false); // display on, cursor off, blink off lcd_hd44780_clear_display(); // be sure the display is cleared lcd_hd44780_write_line(false, lcd_default_line1, strlen(lcd_default_line1)); lcd_hd44780_write_line(true, lcd_default_line2, strlen(lcd_default_line2)); } else { puts("could not start LCD\n"); } // setup OLED display if (!oled_text_setup()) { puts("could not start OLED\n"); } oled_text_clear(); oled_text_update(); // setup FUSB302 to remove all connections to CC pins (Rd enabled by default, also when unpowered) // HW v1 has not been designed with a FUSB302 USB-C controller // a FUSB302 has been added to it to test it in preparation of the v2 // since there wes no free pin to control its power, the LCD LED pin has been reused lcd_hd44780_set_led(false); // provide power to the USB-C controller present_fusb302 = (0 == usb_fusb302_setup()); if (!present_fusb302) { lcd_hd44780_set_led(true); } // setup USB connectors gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0); // only use SWD and reuse JTAG pins rcc_periph_clock_enable(RCC_GPIOA); // enable clock to all GPIO port domains since we use them all rcc_periph_clock_enable(RCC_GPIOB); // enable clock to all GPIO port domains since we use them all rcc_periph_clock_enable(RCC_GPIOC); // enable clock to all GPIO port domains since we use them all rcc_periph_clock_enable(RCC_GPIOD); // enable clock to all GPIO port domains since we use them all rcc_periph_clock_enable(RCC_GPIOE); // enable clock to all GPIO port domains since we use them all rcc_periph_clock_enable(RCC_GPIOF); // enable clock to all GPIO port domains since we use them all rcc_periph_clock_enable(RCC_GPIOG); // enable clock to all GPIO port domains since we use them all usb_cables_pins_float(); // pull all pins to floating // setup terminal terminal_prefix = ""; // set default prefix terminal_process = &process_command; // set central function to process commands terminal_setup(); // start terminal // start main loop uint32_t last_connect_time = rtc_get_counter_val(); // last time a USB cable has been connected/disconnected bool interactive = false; // if there is user activity on the serial port bool action = false; // if an action has been performed don't go to sleep struct cable_t* cable_current = calloc(1, sizeof(struct cable_t)); // to store the currently detected cable if (NULL == cable_current) { puts("EOMEM\n"); while (true); } cable_clear(cable_current); // initialize rest of cable structure struct cable_t* cable_next = calloc(1, sizeof(struct cable_t)); // to store the next detected cable if (NULL == cable_next) { puts("EOMEM\n"); while (true); } cable_clear(cable_next); // initialize rest of cable structure bool cable_changed = false; // if the next cable is not the same as the current uint16_t cable_message_i = 0; // the message index of the last cable message to be displayed uint32_t cable_message_t = last_connect_time; // the time stamp of the last message update interactive = true; while (true) { // infinite loop iwdg_reset(); // kick the dog if (user_input_available) { // user input is available action = true; // action has been performed if (!interactive) { // the user takes control interactive = true; // remember to not shut down anymore // show interactive mode on LCD const char* lcd_interactive_line1 = "interactive mode"; const char* lcd_interactive_line2 = "over serial port"; lcd_hd44780_clear_display(); lcd_hd44780_write_line(false, lcd_interactive_line1, strlen(lcd_interactive_line1)); lcd_hd44780_write_line(true, lcd_interactive_line2, strlen(lcd_interactive_line2)); oled_text_clear(); // nothing to show on additional display oled_text_update(); } char c = user_input_get(); // store receive character terminal_send(c); // send received character to terminal } if (rtc_internal_tick_flag) { // the internal RTC ticked rtc_internal_tick_flag = false; // reset flag action = true; // action has been performed if (!interactive) { // periodically check cable when not in interactive mode // check if there is a cable by testing the ground connection bool ground_connected = usb_cables_test_ground(usb_connectors, LENGTH(usb_connectors), NULL); if (!ground_connected) { // there is no cable if (cable_current->connections_nb > 0) { // there was a cable before lcd_hd44780_clear_display(); // be sure the display is cleared lcd_hd44780_write_line(false, lcd_default_line1, strlen(lcd_default_line1)); lcd_hd44780_write_line(true, lcd_default_line2, strlen(lcd_default_line2)); oled_text_clear(); // nothing to show on additional display oled_text_update(); cable_clear(cable_current); // clear definition } goto test_end; } // there is a cable, start cable detection cable_clear(cable_next); // clear definition cable_detect(cable_next); // detect connected connectors // if there is a cable, we need to identify it further if (cable_next->connections && 0 != cable_next->connections_nb) { cable_connectors(cable_next); // first identify the connectors cable_cables(cable_next); // find cables with matching connector set cable_issues_nb(cable_next); // calculate score for cables (updates the connections) } // compare next to current cable if (cable_current->connections_nb != cable_next->connections_nb) { cable_changed = true; // note it changed, but don't do anything until change is confirmed a second time goto test_end; } // it's a cable with the same number of connections // check if they are all the same connections (the search order is the same) bool match = true; for (uint16_t i = 0; i < cable_current->connections_nb && i < cable_next->connections_nb && match; i++) { if (cable_current->connections[i][0] != cable_next->connections[i][0] || cable_current->connections[i][1] != cable_next->connections[i][1]) { match = false; } } if (!match) { // not the same connections cable_changed = true; } else if (cable_changed) { // it's the same cable, and it has been confirmed a second time cable_changed = false; // remember it's the same cable last_connect_time = rtc_get_counter_val(); // update last connect time to restart the timeout if (0 == cable_current->connections_nb) { // no cable plugged in lcd_hd44780_clear_display(); // be sure the display is cleared lcd_hd44780_write_line(false, lcd_default_line1, strlen(lcd_default_line1)); lcd_hd44780_write_line(true, lcd_default_line2, strlen(lcd_default_line2)); oled_text_clear(); // nothing to show on additional display oled_text_update(); } else { // there is a new confirmed cable cable_load(cable_current); // check if there is a load cable_issues(cable_current); // get the exact issues lcd_hd44780_clear_display(); // clear display oled_text_clear(); // clear additional dispaly if (cable_current->cable_best < LENGTH(usb_cables) && cable_current->cable_best < LENGTH(cable_current->unconnected_nb) && cable_current->cable_best < LENGTH(cable_current->unspecified_nb)) { const struct usb_cable_t* usb_cable = &usb_cables[cable_current->cable_best]; if (usb_cable->shortname) { lcd_hd44780_write_line(false, usb_cable->shortname, strlen(usb_cable->shortname)); } else if (usb_cable->name) { lcd_hd44780_write_line(false, usb_cable->name, strlen(usb_cable->name)); } else { const char* line = "unnamed cable"; lcd_hd44780_write_line(false, line, strlen(line)); } char line[17] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}; // line to display if (cable_current->load) { snprintf(line, LENGTH(line), "with load"); } else { snprintf(line, LENGTH(line), "without load"); } uint16_t issues = cable_current->unconnected_nb[cable_current->cable_best] + cable_current->unspecified_nb[cable_current->cable_best]; if (0 == issues) { const char* line2 = "perfect match"; lcd_hd44780_write_line(true, line2, strlen(line2)); } else { const char* line2 = "closest match"; lcd_hd44780_write_line(true, line2, strlen(line2)); snprintf(line, LENGTH(line), "issues: %u", issues); } snprintf(line, LENGTH(line), "w/%c load", cable_current->load ? 'i' : 'o'); oled_text_line(line, 0); snprintf(line, LENGTH(line), "uncon.: %u", cable_current->unconnected_nb[cable_current->cable_best]); oled_text_line(line, 1); snprintf(line, LENGTH(line), "unspe.: %u", cable_current->unspecified_nb[cable_current->cable_best]); oled_text_line(line, 2); snprintf(line, LENGTH(line), "option: %u", cable_current->optional_nb[cable_current->cable_best]); oled_text_line(line, 3); } else { const char* line1 = "no matching"; const char* line2 = "cable found"; lcd_hd44780_write_line(false, line1, strlen(line1)); lcd_hd44780_write_line(true, line2, strlen(line2)); } oled_text_update(); // update additional display } } else if (cable_current->cable_best < LENGTH(usb_cables) && cable_current->cable_best < LENGTH(cable_current->unconnected_nb) && cable_current->cable_best < LENGTH(cable_current->unspecified_nb)) { // the cable did not change, and there is a valid cable plugged in // fix time (RTC integer overflow is possible, but only happens after 13 years without reset) if (cable_message_t < last_connect_time) { cable_message_t = last_connect_time; cable_message_i = 0; } if (rtc_get_counter_val() >= cable_message_t + RTC_TICKS_SECOND) { // at least a second passed since last message cable_message_t = rtc_get_counter_val(); // remember message has been displayed cable_message_i++; // we will display the next message char line[17] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}; // line to display if (0 == cable_current->unconnected_nb[cable_current->cable_best] && 0 == cable_current->unspecified_nb[cable_current->cable_best]) { // it's a perfect match if (0 == cable_message_i % 2) { // which of the two messages to display snprintf(line, LENGTH(line), "perfect match"); } else { if (cable_current->load) { snprintf(line, LENGTH(line), "with load"); } else { snprintf(line, LENGTH(line), "without load"); } } } else { // not a perfect match if (0 == cable_message_i) { snprintf(line, LENGTH(line), "closest match"); } else if (1 == cable_message_i) { if (cable_current->load) { snprintf(line, LENGTH(line), "with load"); } else { snprintf(line, LENGTH(line), "without load"); } } else if (2 == cable_message_i) { uint16_t issues = cable_next->unconnected_nb[cable_next->cable_best] + cable_next->unspecified_nb[cable_next->cable_best]; snprintf(line, LENGTH(line), "issues: %u", issues); } else if (3 == cable_message_i) { snprintf(line, LENGTH(line), "unconnected: %u", cable_next->unconnected_nb[cable_next->cable_best]); } else if (cable_message_i < 4U + cable_next->unconnected_nb[cable_next->cable_best]) { uint16_t i = cable_message_i - 4U; const struct usb_connector_t* connector_from = usb_cables_get_connector(cable_current->unconnected[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable_current->unconnected[i][1]); if (NULL != connector_from && NULL != connector_to) { snprintf(line, LENGTH(line), "%s_%s %s_%s", connector_from->shortname, usb_pins[cable_current->unconnected[i][0]].name, connector_to->shortname, usb_pins[cable_current->unconnected[i][1]].name); } } else if (cable_message_i == 4U + cable_next->unconnected_nb[cable_next->cable_best]) { snprintf(line, LENGTH(line), "unspecified: %u", cable_next->unspecified_nb[cable_next->cable_best]); } else if (cable_message_i < 5U + cable_next->unconnected_nb[cable_next->cable_best] + cable_next->unspecified_nb[cable_next->cable_best]) { uint16_t i = cable_message_i - 5U - cable_next->unconnected_nb[cable_next->cable_best]; const struct usb_connector_t* connector_from = usb_cables_get_connector(cable_current->unspecified[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable_current->unspecified[i][1]); if (NULL != connector_from && NULL != connector_to) { snprintf(line, LENGTH(line), "%s_%s %s_%s", connector_from->shortname, usb_pins[cable_current->unspecified[i][0]].name, connector_to->shortname, usb_pins[cable_current->unspecified[i][1]].name); } } else if (cable_message_i == 5U + cable_next->unconnected_nb[cable_next->cable_best] + cable_next->unspecified_nb[cable_next->cable_best]) { snprintf(line, LENGTH(line), "optional: %u", cable_next->optional_nb[cable_next->cable_best]); } else if (cable_message_i < 6U + cable_next->unconnected_nb[cable_next->cable_best] + cable_next->unspecified_nb[cable_next->cable_best] + cable_next->optional_nb[cable_next->cable_best]) { uint16_t i = cable_message_i - 6U - cable_next->unconnected_nb[cable_next->cable_best] - cable_next->unspecified_nb[cable_next->cable_best]; const struct usb_connector_t* connector_from = usb_cables_get_connector(cable_current->optional[i][0]); const struct usb_connector_t* connector_to = usb_cables_get_connector(cable_current->optional[i][1]); if (NULL != connector_from && NULL != connector_to) { snprintf(line, LENGTH(line), "%s_%s %s_%s", connector_from->shortname, usb_pins[cable_current->optional[i][0]].name, connector_to->shortname, usb_pins[cable_current->optional[i][1]].name); } } else { // end reached snprintf(line, LENGTH(line), "closest match"); cable_message_i = 0; // restart } } uint8_t len = strlen(line); for (uint8_t i = len; i < LENGTH(line) - 2; i++) { line[i] = ' '; // put space at end of line } line[LENGTH(line) - 1] = '\0'; // end string lcd_hd44780_write_line(true, line, strlen(line)); // write message } } test_end: if (cable_changed) { // next cable because the current one (reuse allocated current for the next) struct cable_t* cable_tmp = cable_current; cable_current = cable_next; cable_next = cable_tmp; } } // !interactive while (!interactive && rtc_get_counter_val() >= last_connect_time + SHUTDOWN_TIMEOUT) { // time to shut down #if !DEBUG PWR_CR |= PWR_CR_DBP; // disable backup domain write protection BKP_DR1 = 0x00ff; // indicate we want to stay in standby mode (it's not possible to disable the independent watchdog and it will reset the system even in standby mode #endif display_off(); // cut power to displays (at stop D+ pull-up to indicate disconnect) standby(); // go into standby mode (shut down) } } if (action) { // go to sleep if nothing had to be done, else recheck for activity action = false; } else { __WFI(); // go to sleep } } // main loop } /** @brief interrupt service routine called when tick passed on RTC */ void rtc_isr(void) { rtc_clear_flag(RTC_SEC); // clear flag rtc_internal_tick_flag = true; // notify to show new time }