stm32f1/application.c

2205 lines
83 KiB
C

/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** CuVoodoo USB cable tester firmware
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities
#include <time.h> // date/time utilities
#include <ctype.h> // utilities to check chars
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/scb.h> // vector table definition
#include <libopencm3/cm3/nvic.h> // interrupt utilities
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/exti.h> // external interrupt utilities
#include <libopencm3/stm32/rtc.h> // real time clock utilities
#include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/f1/bkp.h> // 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("F<P");
if (connections_nb--) {
puts(", ");
}
}
if (connection->rx_drive_float) {
puts("F<D");
if (connections_nb--) {
puts(", ");
}
}
if (connection->rx_drive_pull) {
puts("P<D");
if (connections_nb--) {
puts(", ");
}
}
}
// ===================
// = cable utilities =
// ===================
/** the current cable state */
struct cable_t {
uint16_t connections_nb; // number of connections the cable has
uint8_t (*connections)[2]; // the cable connections (pin pairs)
uint8_t connectors_nb; // number of connectors the cable has
bool connectors[LENGTH(usb_connectors)]; // which connectors the cable connects
bool load; // if there is a load on the cable
uint8_t cables_nb; // number of cable definitions the connectors set match to
bool cables[LENGTH(usb_cables)]; // cable definitions the connectors set match to
uint16_t unconnected_nb[LENGTH(usb_cables)]; // number of unconnected pairs which should be connected according to cable specification
uint16_t unspecified_nb[LENGTH(usb_cables)]; // number of connected pairs which are not specified by cable
uint16_t optional_nb[LENGTH(usb_cables)]; // number of connected pairs which are optional for this cable
uint8_t cable_best; // best matching cable index (e.g. with lowest score)
uint8_t (*unconnected)[2]; // unconnected pairs which should be connected according to best cable specification
uint8_t (*unspecified)[2]; // connected pairs which are not specified by best cable
uint8_t (*optional)[2]; // connected pairs which are optional for best cable
};
/** clear the cable information
* @param[out] cable structure to be cleared
*/
static void cable_clear(struct cable_t* cable)
{
// check input arguments
if (NULL == cable) {
return;
}
// initialize structure
cable->connections_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
}