Skip to content

Commit

Permalink
hardware watchdog
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikolay-Kha committed Jul 23, 2017
1 parent c09fd31 commit 978f5f7
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 8 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions cnc/gmachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from cnc.coordinates import *
from cnc.heater import *
from cnc.enums import *
from cnc.watchdog import *


class GMachineException(Exception):
Expand Down Expand Up @@ -35,6 +36,7 @@ def __init__(self):
self._heaters = dict()
self.reset()
hal.init()
self.watchdog = HardwareWatchdog()

def release(self):
""" Free all resources.
Expand Down
10 changes: 10 additions & 0 deletions cnc/hal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -121,3 +129,5 @@
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 changes: 9 additions & 0 deletions cnc/hal_raspberry/hal.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()
58 changes: 56 additions & 2 deletions cnc/hal_raspberry/rpgpio.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def read(self, pin):
# 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
Expand All @@ -93,7 +94,7 @@ def __init__(self):
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
Expand Down Expand Up @@ -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
Expand All @@ -273,7 +275,8 @@ def __init__(self):
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)
Expand Down Expand Up @@ -356,6 +359,57 @@ def remove_all(self):
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
Expand Down
2 changes: 2 additions & 0 deletions cnc/hal_raspberry/rpgpio_private.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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
Expand All @@ -62,6 +63,7 @@
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)
Expand Down
6 changes: 6 additions & 0 deletions cnc/hal_virtual.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,9 @@ def deinit():
""" De-initialise.
"""
logging.info("hal deinit()")


def watchdog_feed():
""" Feed hardware watchdog.
"""
pass
25 changes: 25 additions & 0 deletions cnc/watchdog.py
Original file line number Diff line number Diff line change
@@ -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...")

0 comments on commit 978f5f7

Please sign in to comment.