hardware watchdog

This commit is contained in:
Nikolay Khabarov
2017-07-23 03:10:27 +03:00
parent c09fd31457
commit 978f5f71d8
8 changed files with 126 additions and 8 deletions
+16 -6
View File
@@ -31,15 +31,25 @@ perfect choice for easy development of this project.
Video demo - [YouTube video](https://youtu.be/41wdmmztTNA) Video demo - [YouTube video](https://youtu.be/41wdmmztTNA)
And the original video when PyCNC was just a prototype [YouTube video](https://youtu.be/vcedo59raS4) And the original video when PyCNC was just a prototype [YouTube video](https://youtu.be/vcedo59raS4)
# Current gcode support # Current gcode and features support
Commands G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92, * Commands G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92,
M2, M3, M5, M30, M84, M104, M105, M106, M107, M109, M114, M140, M190 are M2, M3, M5, M30, M84, M104, M105, M106, M107, M109, M114, M140, M190 are
supported. Commands can be easily added, see [gmachine.py](./cnc/gmachine.py) supported. Commands can be easily added, see [gmachine.py](./cnc/gmachine.py)
file. file.
Four axis are supported - X, Y, Z, E. * Four axis are supported - X, Y, Z, E.
Circular interpolation for XY, ZX, YZ planes is supported. * Circular interpolation for XY, ZX, YZ planes is supported.
Spindle with rpm control is supported. * Spindle with rpm control is supported.
Extruder and bed heaters are supported. * Extruder and bed heaters are supported.
* Hardware watchdog.
# Watchdog
PyCNC uses one of DMA channels as hardware watchdog for safety purpose. If
board, OS or PyCNC hangs this watchdog should disable all GPIO pins(by
switching them into input state, for RPi this would be GPIO0-29) in 15 seconds.
Since there is a high current and dangerous devices like heated bed, extruder
heater, this feature should prevent uncontrollable overheating. But don't count
on such software features too much, they can hang too or output MOSFET become
shorted, use hardware protection like thermal cutoff switches in your machines.
# Hardware # Hardware
Currently, this project supports Raspberry Pi 1-3. Developed and tested with Currently, this project supports Raspberry Pi 1-3. Developed and tested with
+2
View File
@@ -6,6 +6,7 @@ from cnc.pulses import *
from cnc.coordinates import * from cnc.coordinates import *
from cnc.heater import * from cnc.heater import *
from cnc.enums import * from cnc.enums import *
from cnc.watchdog import *
class GMachineException(Exception): class GMachineException(Exception):
@@ -35,6 +36,7 @@ class GMachine(object):
self._heaters = dict() self._heaters = dict()
self.reset() self.reset()
hal.init() hal.init()
self.watchdog = HardwareWatchdog()
def release(self): def release(self):
""" Free all resources. """ Free all resources.
+10
View File
@@ -86,6 +86,14 @@
# """ De-initialise hal, stop any hardware. # """ De-initialise hal, stop any hardware.
# """ # """
# do_something() # do_something()
#
#
# def watchdog_feed():
# """ Feed hardware watchdog. This method should be called at least
# once in 15 seconds. Also, this method can do no operation in hal
# implementation and there will not be emergency stop for heaters.
# """
# do_something()
# check which module to import # check which module to import
@@ -121,3 +129,5 @@ if 'join' not in locals():
raise NotImplementedError("hal.join() not implemented") raise NotImplementedError("hal.join() not implemented")
if 'deinit' not in locals(): if 'deinit' not in locals():
raise NotImplementedError("hal.deinit() not implemented") raise NotImplementedError("hal.deinit() not implemented")
if 'watchdog_feed' not in locals():
raise NotImplementedError("hal.watchdog_feed() not implemented")
+9
View File
@@ -10,6 +10,7 @@ US_IN_SECONDS = 1000000
gpio = rpgpio.GPIO() gpio = rpgpio.GPIO()
dma = rpgpio.DMAGPIO() dma = rpgpio.DMAGPIO()
pwm = rpgpio.DMAPWM() pwm = rpgpio.DMAPWM()
watchdog = rpgpio.DMAWatchdog()
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
@@ -41,6 +42,7 @@ def init():
gpio.clear(EXTRUDER_HEATER_PIN) gpio.clear(EXTRUDER_HEATER_PIN)
gpio.clear(BED_HEATER_PIN) gpio.clear(BED_HEATER_PIN)
gpio.clear(STEPPERS_ENABLE_PIN) gpio.clear(STEPPERS_ENABLE_PIN)
watchdog.start()
def spindle_control(percent): def spindle_control(percent):
@@ -328,3 +330,10 @@ def deinit():
gpio.clear(FAN_PIN) gpio.clear(FAN_PIN)
gpio.clear(EXTRUDER_HEATER_PIN) gpio.clear(EXTRUDER_HEATER_PIN)
gpio.clear(BED_HEATER_PIN) gpio.clear(BED_HEATER_PIN)
watchdog.stop()
def watchdog_feed():
""" Feed hardware watchdog.
"""
watchdog.feed()
+56 -2
View File
@@ -82,6 +82,7 @@ class GPIO(object):
# clock for delay). So, do not create two or more instances of DMAGPIO. # clock for delay). So, do not create two or more instances of DMAGPIO.
class DMAGPIO(DMAProto): class DMAGPIO(DMAProto):
_DMA_CONTROL_BLOCK_SIZE = 32 _DMA_CONTROL_BLOCK_SIZE = 32
_DMA_CHANNEL = 4
def __init__(self): def __init__(self):
""" Create object which control GPIO pins via DMA(Direct Memory """ Create object which control GPIO pins via DMA(Direct Memory
@@ -93,7 +94,7 @@ class DMAGPIO(DMAProto):
otherwise memory will be unlocked and it could be overwritten by otherwise memory will be unlocked and it could be overwritten by
operating system. operating system.
""" """
super(DMAGPIO, self).__init__(30 * 1024 * 1024, 4) super(DMAGPIO, self).__init__(30 * 1024 * 1024, self._DMA_CHANNEL)
self.__current_address = 0 self.__current_address = 0
# get helpers registers, this class uses PWM module to create precise # get helpers registers, this class uses PWM module to create precise
@@ -259,6 +260,7 @@ class DMAPWM(DMAProto):
_DMA_CONTROL_BLOCK_SIZE = 32 _DMA_CONTROL_BLOCK_SIZE = 32
_DMA_DATA_OFFSET = 24 _DMA_DATA_OFFSET = 24
_TOTAL_NUMBER_OF_BLOCKS = 256 _TOTAL_NUMBER_OF_BLOCKS = 256
_DMA_CHANNEL = 14
def __init__(self): def __init__(self):
""" Initialise PWM. PWM has 8 bit resolution and fixed frequency """ Initialise PWM. PWM has 8 bit resolution and fixed frequency
@@ -273,7 +275,8 @@ class DMAPWM(DMAProto):
Cycles in info field of control blocks. Cycles in info field of control blocks.
""" """
super(DMAPWM, self).__init__(self._TOTAL_NUMBER_OF_BLOCKS super(DMAPWM, self).__init__(self._TOTAL_NUMBER_OF_BLOCKS
* self._DMA_CONTROL_BLOCK_SIZE, 14) * self._DMA_CONTROL_BLOCK_SIZE,
self._DMA_CHANNEL)
self._clear_pins = dict() self._clear_pins = dict()
# first control block always set pins # first control block always set pins
self.__add_control_block(0, GPIO_SET_OFFSET) self.__add_control_block(0, GPIO_SET_OFFSET)
@@ -356,6 +359,57 @@ class DMAPWM(DMAProto):
assert len(self._clear_pins) == 0 assert len(self._clear_pins) == 0
class DMAWatchdog(DMAProto):
_DMA_CONTROL_BLOCK_SIZE = 32
_DMA_CHANNEL = 13
_DMA_BLOCKS = 2047
def __init__(self):
""" Initialize hardware watchdog timer.
"""
super(DMAWatchdog, self).__init__(self._DMA_CONTROL_BLOCK_SIZE
* (self._DMA_BLOCKS + 1),
self._DMA_CHANNEL)
def start(self):
""" Arm watchdog for ~15 seconds. If watchdog wasn't fed in 15 seconds,
GPIO pins 0-29 will be switched to input to prevent any output from
them.
"""
data = ()
ba = self._phys_memory.get_bus_address()
# first blocks is just a delay
for _ in range(0, self._DMA_BLOCKS):
data += (
DMA_TI_NO_WIDE_BURSTS | DMA_DEST_IGNORE | DMA_TI_WAIT_RESP
| DMA_TI_WAITS(31),
ba + 24, ba + 28, 65535,
0, ba + self._DMA_CONTROL_BLOCK_SIZE, 0, 0
)
ba += self._DMA_CONTROL_BLOCK_SIZE
# The last block writes zero(switch to input state) in GPIO's FSEL
# register in normal operating, should never be called, until watchdog
# timeout is reached.
data += (
DMA_TI_NO_WIDE_BURSTS | DMA_TI_WAIT_RESP | DMA_TI_DEST_INC,
ba + 24, PHYSICAL_GPIO_BUS + GPIO_FSEL_OFFSET, 12,
0, 0, 0, 0
)
self._phys_memory.write(0, str(len(data)) + "I", data)
super(DMAWatchdog, self)._run_dma()
def stop(self):
""" Disarm watchdog.
"""
super(DMAWatchdog, self)._stop_dma()
def feed(self):
""" Feed watchdog, restart waiting loop from the very beginning.
"""
self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_NEXTCONBK,
self._phys_memory.get_bus_address())
# for testing purpose # for testing purpose
def main(): def main():
pin = 21 pin = 21
+2
View File
@@ -45,6 +45,7 @@ PHYSICAL_GPIO_BUS = 0x7E000000 + GPIO_REGISTER_BASE
DMA_BASE = 0x007000 DMA_BASE = 0x007000
DMA_CS = 0x00 DMA_CS = 0x00
DMA_CONBLK_AD = 0x04 DMA_CONBLK_AD = 0x04
DMA_NEXTCONBK = 0x1C
DMA_TI_NO_WIDE_BURSTS = 1 << 26 DMA_TI_NO_WIDE_BURSTS = 1 << 26
DMA_TI_SRC_INC = 1 << 8 DMA_TI_SRC_INC = 1 << 8
DMA_TI_DEST_INC = 1 << 4 DMA_TI_DEST_INC = 1 << 4
@@ -62,6 +63,7 @@ DMA_CS_ACTIVE = 1 << 0
DMA_TI_PER_MAP_PWM = 5 DMA_TI_PER_MAP_PWM = 5
DMA_TI_PER_MAP_PCM = 2 DMA_TI_PER_MAP_PCM = 2
DMA_TI_PER_MAP = (lambda x: x << 16) DMA_TI_PER_MAP = (lambda x: x << 16)
DMA_TI_WAITS = (lambda x: x << 21)
DMA_TI_TXFR_LEN_YLENGTH = (lambda y: (y & 0x3fff) << 16) DMA_TI_TXFR_LEN_YLENGTH = (lambda y: (y & 0x3fff) << 16)
DMA_TI_TXFR_LEN_XLENGTH = (lambda x: x & 0xffff) DMA_TI_TXFR_LEN_XLENGTH = (lambda x: x & 0xffff)
DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16) DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16)
+6
View File
@@ -196,3 +196,9 @@ def deinit():
""" De-initialise. """ De-initialise.
""" """
logging.info("hal deinit()") logging.info("hal deinit()")
def watchdog_feed():
""" Feed hardware watchdog.
"""
pass
+25
View File
@@ -0,0 +1,25 @@
import threading
import time
from cnc import hal
class HardwareWatchdog(threading.Thread):
def __init__(self):
""" Run feed loop for hardware watchdog.
"""
super(HardwareWatchdog, self).__init__()
self.setDaemon(True)
self.start()
def run(self):
while True:
hal.watchdog_feed()
time.sleep(3)
# for test purpose
if __name__ == "__main__":
hal.init()
hal.fan_control(True)
print("Fan is on, it should turn off automatically in ~15 seconds."
"\nExiting...")