mirror of
https://github.com/sinseman44/PyCNC.git
synced 2026-07-16 08:37:09 +00:00
hardware watchdog
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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...")
|
||||||
Reference in New Issue
Block a user