hub-ctrl: Add support for micro-controller reset via RPi usb power toggling

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2017-04-13 12:09:37 -04:00
parent 9f9e3e61d6
commit daff83ee9a
6 changed files with 480 additions and 22 deletions

View File

@ -245,9 +245,12 @@ pin_map: arduino
# default is to not enable the aliases. # default is to not enable the aliases.
#restart_method: arduino #restart_method: arduino
# This controls the mechanism the host will use to reset the # This controls the mechanism the host will use to reset the
# micro-controller. The choices are 'arduino' and 'command'. The # micro-controller. The choices are 'arduino', 'rpi_usb', and
# 'arduino' method (toggle DTR; set baud to 1200) is common on # 'command'. The 'arduino' method (toggle DTR; set baud to 1200) is
# Arduino boards and clones. The 'command' method involves sending a # common on Arduino boards and clones. The 'rpi_usb' method is
# useful on Raspberry Pi boards with micro-controllers powered over
# USB - it briefly disables power to all USB ports to accomplish a
# micro-controller reset. The 'command' method involves sending a
# Klipper command to the micro-controller so that it can reset # Klipper command to the micro-controller so that it can reset
# itself. The default is 'arduino'. # itself. The default is 'arduino'.
custom: custom:

View File

@ -24,7 +24,7 @@ following commands:
``` ```
sudo apt-get update sudo apt-get update
sudo apt-get install libncurses-dev sudo apt-get install libncurses-dev libusb-dev
sudo apt-get install avrdude gcc-avr binutils-avr avr-libc # AVR toolchain sudo apt-get install avrdude gcc-avr binutils-avr avr-libc # AVR toolchain
sudo apt-get install bossa-cli libnewlib-arm-none-eabi # ARM toolchain sudo apt-get install bossa-cli libnewlib-arm-none-eabi # ARM toolchain
``` ```

View File

@ -1,11 +1,16 @@
# Wrapper around C helper code # Wrapper around C helper code
# #
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import os, logging import os, logging
import cffi import cffi
######################################################################
# c_helper.so compiling
######################################################################
COMPILE_CMD = "gcc -Wall -g -O2 -shared -fPIC -o %s %s" COMPILE_CMD = "gcc -Wall -g -O2 -shared -fPIC -o %s %s"
SOURCE_FILES = ['stepcompress.c', 'serialqueue.c', 'pyhelper.c'] SOURCE_FILES = ['stepcompress.c', 'serialqueue.c', 'pyhelper.c']
DEST_LIB = "c_helper.so" DEST_LIB = "c_helper.so"
@ -79,14 +84,14 @@ def get_mtimes(srcdir, filelist):
return out return out
# Check if the code needs to be compiled # Check if the code needs to be compiled
def check_build_code(srcdir): def check_build_code(srcdir, target, sources, cmd, other_files=[]):
src_times = get_mtimes(srcdir, SOURCE_FILES + OTHER_FILES) src_times = get_mtimes(srcdir, sources + other_files)
obj_times = get_mtimes(srcdir, [DEST_LIB]) obj_times = get_mtimes(srcdir, [target])
if not obj_times or max(src_times) > min(obj_times): if not obj_times or max(src_times) > min(obj_times):
logging.info("Building C code module") logging.info("Building C code module %s" % (target,))
srcfiles = [os.path.join(srcdir, fname) for fname in SOURCE_FILES] srcfiles = [os.path.join(srcdir, fname) for fname in sources]
destlib = os.path.join(srcdir, DEST_LIB) destlib = os.path.join(srcdir, target)
os.system(COMPILE_CMD % (destlib, ' '.join(srcfiles))) os.system(cmd % (destlib, ' '.join(srcfiles)))
FFI_main = None FFI_main = None
FFI_lib = None FFI_lib = None
@ -97,7 +102,8 @@ def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback global FFI_main, FFI_lib, pyhelper_logging_callback
if FFI_lib is None: if FFI_lib is None:
srcdir = os.path.dirname(os.path.realpath(__file__)) srcdir = os.path.dirname(os.path.realpath(__file__))
check_build_code(srcdir) check_build_code(srcdir, DEST_LIB, SOURCE_FILES, COMPILE_CMD
, OTHER_FILES)
FFI_main = cffi.FFI() FFI_main = cffi.FFI()
FFI_main.cdef(defs_stepcompress) FFI_main.cdef(defs_stepcompress)
FFI_main.cdef(defs_serialqueue) FFI_main.cdef(defs_serialqueue)
@ -110,3 +116,20 @@ def get_ffi():
"void(const char *)", logging_callback) "void(const char *)", logging_callback)
FFI_lib.set_python_logging_callback(pyhelper_logging_callback) FFI_lib.set_python_logging_callback(pyhelper_logging_callback)
return FFI_main, FFI_lib return FFI_main, FFI_lib
######################################################################
# hub-ctrl hub power controller
######################################################################
HC_COMPILE_CMD = "gcc -Wall -g -O2 -o %s %s -lusb"
HC_SOURCE_FILES = ['hub-ctrl.c']
HC_SOURCE_DIR = '../lib/hub-ctrl'
HC_TARGET = "hub-ctrl"
HC_CMD = "sudo %s/hub-ctrl -h 0 -P 2 -p %d"
def run_hub_ctrl(enable_power):
srcdir = os.path.dirname(os.path.realpath(__file__))
hubdir = os.path.join(srcdir, HC_SOURCE_DIR)
check_build_code(hubdir, HC_TARGET, HC_SOURCE_FILES, HC_COMPILE_CMD)
os.system(HC_CMD % (hubdir, enable_power))

View File

@ -1,9 +1,9 @@
# Multi-processor safe interface to micro-controller # Multi-processor safe interface to micro-controller
# #
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import sys, zlib, logging, math import sys, os, zlib, logging, math
import serialhdl, pins, chelper import serialhdl, pins, chelper
class error(Exception): class error(Exception):
@ -381,7 +381,7 @@ class MCU:
self._is_fileoutput = False self._is_fileoutput = False
self._timeout_timer = printer.reactor.register_timer( self._timeout_timer = printer.reactor.register_timer(
self.timeout_handler) self.timeout_handler)
rmethods = {m: m for m in ['arduino', 'command']} rmethods = {m: m for m in ['arduino', 'command', 'rpi_usb']}
self._restart_method = config.getchoice( self._restart_method = config.getchoice(
'restart_method', rmethods, 'arduino') 'restart_method', rmethods, 'arduino')
# Config building # Config building
@ -424,8 +424,19 @@ class MCU:
self.serial.dump_debug() self.serial.dump_debug()
self._printer.note_shutdown(self._shutdown_msg) self._printer.note_shutdown(self._shutdown_msg)
# Connection phase # Connection phase
def _check_restart(self, reason):
if self._printer.get_startup_state() == 'firmware_restart':
return
logging.info("Attempting automated firmware restart: %s" % (reason,))
self._printer.request_exit('firmware_restart')
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
raise error("Attempt firmware restart failed")
def connect(self): def connect(self):
if not self._is_fileoutput: if not self._is_fileoutput:
if (self._restart_method == 'rpi_usb'
and not os.path.exists(self._serialport)):
# Try toggling usb power
self._check_restart("enable power")
self.serial.connect() self.serial.connect()
self._printer.reactor.update_timer( self._printer.reactor.update_timer(
self._timeout_timer, self.monotonic() + self.COMM_TIMEOUT) self._timeout_timer, self.monotonic() + self.COMM_TIMEOUT)
@ -478,6 +489,13 @@ class MCU:
self.send(self._clear_shutdown_cmd.encode()) self.send(self._clear_shutdown_cmd.encode())
def microcontroller_restart(self): def microcontroller_restart(self):
reactor = self._printer.reactor reactor = self._printer.reactor
if self._restart_method == 'rpi_usb':
logging.info("Attempting a microcontroller reset via rpi usb power")
self.disconnect()
chelper.run_hub_ctrl(0)
reactor.pause(reactor.monotonic() + 2.000)
chelper.run_hub_ctrl(1)
return
if self._restart_method == 'command': if self._restart_method == 'command':
last_clock, last_clock_time = self.serial.get_last_clock() last_clock, last_clock_time = self.serial.get_last_clock()
eventtime = reactor.monotonic() eventtime = reactor.monotonic()
@ -539,6 +557,9 @@ class MCU:
else: else:
config_params = self.serial.send_with_response(msg, 'config') config_params = self.serial.send_with_response(msg, 'config')
if not config_params['is_config']: if not config_params['is_config']:
if self._restart_method == 'rpi_usb':
# Only configure mcu after usb power reset
self._check_restart("full reset before config")
# Send config commands # Send config commands
logging.info("Sending printer configuration...") logging.info("Sending printer configuration...")
for c in self._config_cmds: for c in self._config_cmds:
@ -551,12 +572,7 @@ class MCU:
self._shutdown_msg,)) self._shutdown_msg,))
raise error("Unable to configure printer") raise error("Unable to configure printer")
if self._config_crc != config_params['crc']: if self._config_crc != config_params['crc']:
if self._printer.get_startup_state() != 'firmware_restart': self._check_restart("CRC mismatch")
# Attempt a firmware restart to fix the CRC error
logging.info(
"Printer CRC mismatch - attempting firmware restart")
self._printer.request_exit('firmware_restart')
self._printer.reactor.pause(0.100)
raise error("Printer CRC does not match config") raise error("Printer CRC does not match config")
move_count = config_params['move_count'] move_count = config_params['move_count']
logging.info("Configured (%d moves)" % (move_count,)) logging.info("Configured (%d moves)" % (move_count,))

View File

@ -10,3 +10,7 @@ The cmsis-sam3x8e directory contains code from the Arduino project:
version 1.5.1 (extracted on 20160608). It has been modified to version 1.5.1 (extracted on 20160608). It has been modified to
compile with gcc's LTO feature. See cmsis-sam3x8e.patch for the compile with gcc's LTO feature. See cmsis-sam3x8e.patch for the
modifications. modifications.
The hub-ctrl directory contains code from:
https://github.com/codazoda/hub-ctrl.c/
revision 42095e522859059e8a5f4ec05c1e3def01a870a9.

412
lib/hub-ctrl/hub-ctrl.c Normal file
View File

@ -0,0 +1,412 @@
/*
* Copyright (C) 2006 Free Software Initiative of Japan
*
* Author: NIIBE Yutaka <gniibe at fsij.org>
*
* This file can be distributed under the terms and conditions of the
* GNU General Public License version 2 (or later).
*
*/
#include <errno.h>
#include <usb.h>
#include <stdio.h>
#include <string.h>
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE)
#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER)
#define USB_PORT_FEAT_POWER 8
#define USB_PORT_FEAT_INDICATOR 22
#define USB_DIR_IN 0x80 /* to host */
#define COMMAND_SET_NONE 0
#define COMMAND_SET_LED 1
#define COMMAND_SET_POWER 2
#define HUB_LED_GREEN 2
static void
usage (const char *progname)
{
fprintf (stderr,
"Usage: %s [{-h HUBNUM | -b BUSNUM -d DEVNUM}] \\\n"
" [-P PORT] [{-p [VALUE]|-l [VALUE]}]\n", progname);
}
static void
exit_with_usage (const char *progname)
{
usage (progname);
exit (1);
}
#define HUB_CHAR_LPSM 0x0003
#define HUB_CHAR_PORTIND 0x0080
struct usb_hub_descriptor {
unsigned char bDescLength;
unsigned char bDescriptorType;
unsigned char bNbrPorts;
unsigned char wHubCharacteristics[2];
unsigned char bPwrOn2PwrGood;
unsigned char bHubContrCurrent;
unsigned char data[0];
};
#define CTRL_TIMEOUT 1000
#define USB_STATUS_SIZE 4
#define MAX_HUBS 128
struct hub_info {
int busnum, devnum;
struct usb_device *dev;
int nport;
int indicator_support;
};
static struct hub_info hubs[MAX_HUBS];
static int number_of_hubs_with_feature;
static void
hub_port_status (usb_dev_handle *uh, int nport)
{
int i;
printf(" Hub Port Status:\n");
for (i = 0; i < nport; i++)
{
char buf[USB_STATUS_SIZE];
int ret;
ret = usb_control_msg (uh,
USB_ENDPOINT_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_GET_STATUS,
0, i + 1,
buf, USB_STATUS_SIZE,
CTRL_TIMEOUT);
if (ret < 0)
{
fprintf (stderr,
"cannot read port %d status, %s (%d)\n",
i + 1, strerror(errno), errno);
break;
}
printf(" Port %d: %02x%02x.%02x%02x", i + 1,
buf[3], buf [2],
buf[1], buf [0]);
printf("%s%s%s%s%s",
(buf[2] & 0x10) ? " C_RESET" : "",
(buf[2] & 0x08) ? " C_OC" : "",
(buf[2] & 0x04) ? " C_SUSPEND" : "",
(buf[2] & 0x02) ? " C_ENABLE" : "",
(buf[2] & 0x01) ? " C_CONNECT" : "");
printf("%s%s%s%s%s%s%s%s%s%s\n",
(buf[1] & 0x10) ? " indicator" : "",
(buf[1] & 0x08) ? " test" : "",
(buf[1] & 0x04) ? " highspeed" : "",
(buf[1] & 0x02) ? " lowspeed" : "",
(buf[1] & 0x01) ? " power" : "",
(buf[0] & 0x10) ? " RESET" : "",
(buf[0] & 0x08) ? " oc" : "",
(buf[0] & 0x04) ? " suspend" : "",
(buf[0] & 0x02) ? " enable" : "",
(buf[0] & 0x01) ? " connect" : "");
}
}
static int
usb_find_hubs (int listing, int verbose, int busnum, int devnum, int hub)
{
struct usb_bus *busses;
struct usb_bus *bus;
number_of_hubs_with_feature = 0;
busses = usb_get_busses();
if (busses == NULL)
{
perror ("failed to access USB");
return -1;
}
for (bus = busses; bus; bus = bus->next)
{
struct usb_device *dev;
for (dev = bus->devices; dev; dev = dev->next)
{
usb_dev_handle *uh;
int print = 0;
if (dev->descriptor.bDeviceClass != USB_CLASS_HUB)
continue;
if (listing
|| (verbose
&& ((atoi (bus->dirname) == busnum && dev->devnum == devnum)
|| hub == number_of_hubs_with_feature)))
print = 1;
uh = usb_open (dev);
if (uh != NULL)
{
char buf[1024];
int len;
int nport;
struct usb_hub_descriptor *uhd = (struct usb_hub_descriptor *)buf;
if ((len = usb_control_msg (uh, USB_DIR_IN | USB_RT_HUB,
USB_REQ_GET_DESCRIPTOR,
USB_DT_HUB << 8, 0,
buf, sizeof (buf), CTRL_TIMEOUT))
> sizeof (struct usb_hub_descriptor))
{
if (!(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND)
&& (uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM) >= 2)
continue;
if (print)
printf ("Hub #%d at %s:%03d\n",
number_of_hubs_with_feature,
bus->dirname, dev->devnum);
switch ((uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM))
{
case 0:
if (print)
fprintf (stderr, " INFO: ganged switching.\n");
break;
case 1:
if (print)
fprintf (stderr, " INFO: individual power switching.\n");
break;
case 2:
case 3:
if (print)
fprintf (stderr, " WARN: No power switching.\n");
break;
}
if (print
&& !(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND))
fprintf (stderr, " WARN: Port indicators are NOT supported.\n");
}
else
{
perror ("Can't get hub descriptor");
usb_close (uh);
continue;
}
nport = buf[2];
hubs[number_of_hubs_with_feature].busnum = atoi (bus->dirname);
hubs[number_of_hubs_with_feature].devnum = dev->devnum;
hubs[number_of_hubs_with_feature].dev = dev;
hubs[number_of_hubs_with_feature].indicator_support =
(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND)? 1 : 0;
hubs[number_of_hubs_with_feature].nport = nport;
number_of_hubs_with_feature++;
if (verbose)
hub_port_status (uh, nport);
usb_close (uh);
}
}
}
return number_of_hubs_with_feature;
}
int
get_hub (int busnum, int devnum)
{
int i;
for (i = 0; i < number_of_hubs_with_feature; i++)
if (hubs[i].busnum == busnum && hubs[i].devnum == devnum)
return i;
return -1;
}
/*
* HUB-CTRL - program to control port power/led of USB hub
*
* # hub-ctrl // List hubs available
* # hub-ctrl -P 1 // Power off at port 1
* # hub-ctrl -P 1 -p 1 // Power on at port 1
* # hub-ctrl -P 2 -l // LED on at port 1
*
* Requirement: USB hub which implements port power control / indicator control
*
* Work fine:
* Elecom's U2H-G4S: www.elecom.co.jp (indicator depends on power)
* 04b4:6560
*
* Sanwa Supply's USB-HUB14GPH: www.sanwa.co.jp (indicators don't)
*
* Targus, Inc.'s PAUH212: www.targus.com (indicators don't)
* 04cc:1521
*
* Hawking Technology's UH214: hawkingtech.com (indicators don't)
*
*/
int
main (int argc, const char *argv[])
{
int busnum = 0, devnum = 0;
int cmd = COMMAND_SET_NONE;
int port = 1;
int value = 0;
int request, feature, index;
int result = 0;
int listing = 0;
int verbose = 0;
int hub = -1;
usb_dev_handle *uh = NULL;
int i;
if (argc == 1)
listing = 1;
for (i = 1; i < argc; i++)
if (argv[i][0] == '-')
switch (argv[i][1])
{
case 'h':
if (++i >= argc || busnum > 0 || devnum > 0)
exit_with_usage (argv[0]);
hub = atoi (argv[i]);
break;
case 'b':
if (++i >= argc || hub >= 0)
exit_with_usage (argv[0]);
busnum = atoi (argv[i]);
break;
case 'd':
if (++i >= argc || hub >= 0)
exit_with_usage (argv[0]);
devnum = atoi (argv[i]);
break;
case 'P':
if (++i >= argc)
exit_with_usage (argv[0]);
port = atoi (argv[i]);
break;
case 'l':
if (cmd != COMMAND_SET_NONE)
exit_with_usage (argv[0]);
if (++i < argc)
value = atoi (argv[i]);
else
value = HUB_LED_GREEN;
cmd = COMMAND_SET_LED;
break;
case 'p':
if (cmd != COMMAND_SET_NONE)
exit_with_usage (argv[0]);
if (++i < argc)
value = atoi (argv[i]);
else
value= 0;
cmd = COMMAND_SET_POWER;
break;
case 'v':
verbose = 1;
if (argc == 2)
listing = 1;
break;
default:
exit_with_usage (argv[0]);
}
else
exit_with_usage (argv[0]);
if ((busnum > 0 && devnum <= 0) || (busnum <= 0 && devnum > 0))
/* BUS is specified, but DEV is'nt, or ... */
exit_with_usage (argv[0]);
/* Default is the hub #0 */
if (hub < 0 && busnum == 0)
hub = 0;
/* Default is POWER */
if (cmd == COMMAND_SET_NONE)
cmd = COMMAND_SET_POWER;
usb_init ();
usb_find_busses ();
usb_find_devices ();
if (usb_find_hubs (listing, verbose, busnum, devnum, hub) <= 0)
{
fprintf (stderr, "No hubs found.\n");
exit (1);
}
if (listing)
exit (0);
if (hub < 0)
hub = get_hub (busnum, devnum);
if (hub >= 0 && hub < number_of_hubs_with_feature)
uh = usb_open (hubs[hub].dev);
if (uh == NULL)
{
fprintf (stderr, "Device not found.\n");
result = 1;
}
else
{
if (cmd == COMMAND_SET_POWER)
if (value)
{
request = USB_REQ_SET_FEATURE;
feature = USB_PORT_FEAT_POWER;
index = port;
}
else
{
request = USB_REQ_CLEAR_FEATURE;
feature = USB_PORT_FEAT_POWER;
index = port;
}
else
{
request = USB_REQ_SET_FEATURE;
feature = USB_PORT_FEAT_INDICATOR;
index = (value << 8) | port;
}
if (verbose)
printf ("Send control message (REQUEST=%d, FEATURE=%d, INDEX=%d)\n",
request, feature, index);
if (usb_control_msg (uh, USB_RT_PORT, request, feature, index,
NULL, 0, CTRL_TIMEOUT) < 0)
{
perror ("failed to control.\n");
result = 1;
}
if (verbose)
hub_port_status (uh, hubs[hub].nport);
usb_close (uh);
}
exit (result);
}