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)
And the original video when PyCNC was just a prototype [YouTube video](https://youtu.be/vcedo59raS4)
# Current gcode support
Commands G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92,
# Current gcode and features support
* 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
supported. Commands can be easily added, see [gmachine.py](./cnc/gmachine.py)
file.
Four axis are supported - X, Y, Z, E.
Circular interpolation for XY, ZX, YZ planes is supported.
Spindle with rpm control is supported.
Extruder and bed heaters are supported.
* Four axis are supported - X, Y, Z, E.
* Circular interpolation for XY, ZX, YZ planes is supported.
* Spindle with rpm control is 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
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.heater import *
from cnc.enums import *
from cnc.watchdog import *
class GMachineException(Exception):
@@ -35,6 +36,7 @@ class GMachine(object):
self._heaters = dict()
self.reset()
hal.init()
self.watchdog = HardwareWatchdog()
def release(self):
""" Free all resources.
+10
View File
@@ -86,6 +86,14 @@
# """ De-initialise hal, stop any hardware.
# """
# 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
@@ -121,3 +129,5 @@ if 'join' not in locals():
raise NotImplementedError("hal.join() not implemented")
if 'deinit' not in locals():
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()
dma = rpgpio.DMAGPIO()
pwm = rpgpio.DMAPWM()
watchdog = rpgpio.DMAWatchdog()
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
@@ -41,6 +42,7 @@ def init():
gpio.clear(EXTRUDER_HEATER_PIN)
gpio.clear(BED_HEATER_PIN)
gpio.clear(STEPPERS_ENABLE_PIN)
watchdog.start()
def spindle_control(percent):
@@ -328,3 +330,10 @@ def deinit():
gpio.clear(FAN_PIN)
gpio.clear(EXTRUDER_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.
class DMAGPIO(DMAProto):
_DMA_CONTROL_BLOCK_SIZE = 32
_DMA_CHANNEL = 4
def __init__(self):
""" 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
operating system.
"""
super(DMAGPIO, self).__init__(30 * 1024 * 1024, 4)
super(DMAGPIO, self).__init__(30 * 1024 * 1024, self._DMA_CHANNEL)
self.__current_address = 0
# get helpers registers, this class uses PWM module to create precise
@@ -259,6 +260,7 @@ class DMAPWM(DMAProto):
_DMA_CONTROL_BLOCK_SIZE = 32
_DMA_DATA_OFFSET = 24
_TOTAL_NUMBER_OF_BLOCKS = 256
_DMA_CHANNEL = 14
def __init__(self):
""" Initialise PWM. PWM has 8 bit resolution and fixed frequency
@@ -273,7 +275,8 @@ class DMAPWM(DMAProto):
Cycles in info field of control 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()
# first control block always set pins
self.__add_control_block(0, GPIO_SET_OFFSET)
@@ -356,6 +359,57 @@ class DMAPWM(DMAProto):
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
def main():
pin = 21
+2
View File
@@ -45,6 +45,7 @@ PHYSICAL_GPIO_BUS = 0x7E000000 + GPIO_REGISTER_BASE
DMA_BASE = 0x007000
DMA_CS = 0x00
DMA_CONBLK_AD = 0x04
DMA_NEXTCONBK = 0x1C
DMA_TI_NO_WIDE_BURSTS = 1 << 26
DMA_TI_SRC_INC = 1 << 8
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_PCM = 2
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_XLENGTH = (lambda x: x & 0xffff)
DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16)
+6
View File
@@ -196,3 +196,9 @@ def deinit():
""" De-initialise.
"""
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...")