diff --git a/README.md b/README.md index cd6b9e7..c6fba3b 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ Video demo - [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, -M2, M3, M5, M30, M104, M105, M106, M107, M109, M114, M140, M190 are supported. -Commands can be easily added, see [gmachine.py](./cnc/gmachine.py) file. +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. diff --git a/cnc/config.py b/cnc/config.py index 4b91ab1..147f27f 100644 --- a/cnc/config.py +++ b/cnc/config.py @@ -52,6 +52,8 @@ BED_PID = {"P": 5.06820175723, # ----------------------------------------------------------------------------- # Pins configuration. +# Enable pin for all steppers, low level is enabled. +STEPPERS_ENABLE_PIN = 26 STEPPER_STEP_PIN_X = 16 STEPPER_STEP_PIN_Y = 20 STEPPER_STEP_PIN_Z = 21 diff --git a/cnc/gmachine.py b/cnc/gmachine.py index e79b7f0..5db3f45 100644 --- a/cnc/gmachine.py +++ b/cnc/gmachine.py @@ -220,8 +220,8 @@ class GMachine(object): logging.info("Moving circularly {} {} {} with radius {}" " and velocity {}".format(self._plane, circle_end, direction, radius, velocity)) - gen = PulseGeneratorCircular(circle_end, radius, self._plane, direction, - velocity) + gen = PulseGeneratorCircular(circle_end, radius, self._plane, + direction, velocity) self.__check_velocity(gen.max_velocity()) # if finish coords is not on circle, move some distance linearly linear_delta = delta - circle_end @@ -411,6 +411,8 @@ class GMachine(object): self._spindle(0) elif c == 'M2' or c == 'M30': # program finish, reset everything. self.reset() + elif c == 'M84': # disable motors + hal.disable_steppers() # extruder and bed heaters control elif c == 'M104' or c == 'M109' or c == 'M140' or c == 'M190': if c == 'M104' or c == 'M109': @@ -453,7 +455,7 @@ class GMachine(object): hal.join() p = self.position() answer = "X:{} Y:{} Z:{} E:{}".format(p.x, p.y, p.z, p.e) - elif c is None: # command not specified(for example, just F was passed) + elif c is None: # command not specified(ie just F was passed) pass # commands below are added just for compatibility elif c == 'M82': # absolute mode for extruder @@ -462,8 +464,6 @@ class GMachine(object): elif c == 'M83': # relative mode for extruder if self._absoluteCoordinates: raise GMachineException("Not supported, use G90/G91") - elif c == 'M84': # disable motors - pass # do not do anything else: raise GMachineException("unknown command") # save parameters on success diff --git a/cnc/hal.py b/cnc/hal.py index 18bb88d..7c0b2e9 100644 --- a/cnc/hal.py +++ b/cnc/hal.py @@ -51,6 +51,13 @@ # """ # return measure() # +# +# def disable_steppers(): +# """ Disable all steppers until any movement occurs. +# """ +# do_something() +# +# # def calibrate(x, y, z): # """ Move head to home position till end stop switch will be triggered. # Do not return till all procedures are completed. @@ -61,6 +68,7 @@ # """ # return do_something() # +# # def move(generator): # """ Move head to according pulses in PulseGenerator. # :param generator: PulseGenerator object @@ -103,6 +111,8 @@ if 'get_extruder_temperature' not in locals(): raise NotImplementedError("hal.get_extruder_temperature() not implemented") if 'get_bed_temperature' not in locals(): raise NotImplementedError("hal.get_bed_temperature() not implemented") +if 'disable_steppers' not in locals(): + raise NotImplementedError("hal.disable_steppers() not implemented") if 'calibrate' not in locals(): raise NotImplementedError("hal.calibrate() not implemented") if 'move' not in locals(): diff --git a/cnc/hal_raspberry/hal.py b/cnc/hal_raspberry/hal.py index 9f61d2c..253b8a0 100644 --- a/cnc/hal_raspberry/hal.py +++ b/cnc/hal_raspberry/hal.py @@ -35,10 +35,12 @@ def init(): gpio.init(FAN_PIN, rpgpio.GPIO.MODE_OUTPUT) gpio.init(EXTRUDER_HEATER_PIN, rpgpio.GPIO.MODE_OUTPUT) gpio.init(BED_HEATER_PIN, rpgpio.GPIO.MODE_OUTPUT) + gpio.init(STEPPERS_ENABLE_PIN, rpgpio.GPIO.MODE_OUTPUT) gpio.clear(SPINDLE_PWM_PIN) gpio.clear(FAN_PIN) gpio.clear(EXTRUDER_HEATER_PIN) gpio.clear(BED_HEATER_PIN) + gpio.clear(STEPPERS_ENABLE_PIN) def spindle_control(percent): @@ -99,6 +101,13 @@ def get_bed_temperature(): return thermistor.get_temperature(BED_TEMPERATURE_SENSOR_CHANNEL) +def disable_steppers(): + """ Disable all steppers until any movement occurs. + """ + logging.info("disable steppers") + gpio.set(STEPPERS_ENABLE_PIN) + + def __calibrate_private(x, y, z, invert): if invert: stepper_inverted_x = not STEPPER_INVERTED_X @@ -190,6 +199,8 @@ def calibrate(x, y, z): :param z: boolean, True to calibrate Z axis. :return: boolean, True if all specified end stops were triggered. """ + # enable steppers + gpio.clear(STEPPERS_ENABLE_PIN) logging.info("hal calibrate, x={}, y={}, z={}".format(x, y, z)) if not __calibrate_private(x, y, z, True): # move from endstop switch return False @@ -206,6 +217,8 @@ def move(generator): # moving. In this case machine would safely paused between commands until # calculation is done. + # enable steppers + gpio.clear(STEPPERS_ENABLE_PIN) # 4 control blocks per 32 bytes bytes_per_iter = 4 * dma.control_block_size() # prepare and run dma @@ -309,6 +322,7 @@ def deinit(): """ De-initialize hardware. """ join() + disable_steppers() pwm.remove_all() gpio.clear(SPINDLE_PWM_PIN) gpio.clear(FAN_PIN) diff --git a/cnc/hal_raspberry/rpgpio.py b/cnc/hal_raspberry/rpgpio.py index b9ed677..0886f2e 100755 --- a/cnc/hal_raspberry/rpgpio.py +++ b/cnc/hal_raspberry/rpgpio.py @@ -15,8 +15,8 @@ class GPIO(object): def __init__(self): """ Create object which can control GPIO. - This class writes directly to CPU registers and doesn't use any libs - or kernel modules. + This class writes directly to CPU registers and doesn't use any + libs or kernel modules. """ self._mem = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE) @@ -191,7 +191,7 @@ class DMAGPIO(DMAProto): self._phys_memory.write_int(self.__current_address + 20 - self._DMA_CONTROL_BLOCK_SIZE, 0) logging.info("DMA took {}MB of memory". - format(round(self.__current_address / 1024.0 / 1024.0, 2))) + format(round(self.__current_address / 1048576.0, 2))) def run_stream(self): """ Run DMA module in stream mode, i.e. does'n finalize last block @@ -199,18 +199,19 @@ class DMAGPIO(DMAProto): """ # configure PWM hardware module which will clocks DMA self._pwm.write_int(PWM_CTL, 0) - self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD) # disable + # disable + self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD) while (self._clock.read_int(CM_PWM_CNTL) & CM_CNTL_BUSY) != 0: time.sleep(0.00001) # 10 us, wait until BUSY bit is clear - self._clock.write_int(CM_PWM_DIV, - CM_PASSWORD | CM_DIV_VALUE(5)) # 100MHz + # configure, 100 MHz + self._clock.write_int(CM_PWM_DIV, CM_PASSWORD | CM_DIV_VALUE(5)) self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD | CM_CNTL_ENABLE) - self._pwm.write_int(PWM_RNG1, 100) - self._pwm.write_int(PWM_DMAC, PWM_DMAC_ENAB - | PWM_DMAC_PANIC(15) | PWM_DMAC_DREQ(15)) + self._pwm.write_int(PWM_DMAC, PWM_DMAC_ENAB | PWM_DMAC_PANIC(15) + | PWM_DMAC_DREQ(15)) self._pwm.write_int(PWM_CTL, PWM_CTL_CLRF) + # enable self._pwm.write_int(PWM_CTL, PWM_CTL_USEF1 | PWM_CTL_PWEN1) super(DMAGPIO, self)._run_dma() diff --git a/cnc/hal_virtual.py b/cnc/hal_virtual.py index 55e2982..973157e 100644 --- a/cnc/hal_virtual.py +++ b/cnc/hal_virtual.py @@ -62,6 +62,12 @@ def get_bed_temperature(): return BED_MAX_TEMPERATURE * 0.999 +def disable_steppers(): + """ Disable all steppers until any movement occurs. + """ + logging.info("hal disable steppers") + + def calibrate(x, y, z): """ Move head to home position till end stop switch will be triggered. Do not return till all procedures are completed. diff --git a/cnc/heater.py b/cnc/heater.py index b3a636a..894f3b9 100644 --- a/cnc/heater.py +++ b/cnc/heater.py @@ -61,8 +61,8 @@ class Heater(threading.Thread): last_error = time.time() else: if time.time() - last_error > self.SENSOR_TIMEOUT_S: - logging.critical("No data from temperature sensor. Stop" - " heating.") + logging.critical("No data from temperature sensor." + " Stop heating.") break continue last_error = None @@ -87,8 +87,8 @@ class Heater(threading.Thread): i = 0 while not self._pid.is_fixed(): if i % 8 == 0: - logging.info("Heating... current temperature {} C, power {}%" - .format(self._measure(), int(self._current_power))) + logging.info("Heating... current temperature {} C, power {}%". + format(self._measure(), int(self._current_power))) i = 0 i += 1 time.sleep(0.25) diff --git a/cnc/pulses.py b/cnc/pulses.py index 3bbc35a..56126b6 100644 --- a/cnc/pulses.py +++ b/cnc/pulses.py @@ -609,9 +609,10 @@ class PulseGeneratorCircular(PulseGenerator): def __circular_a(self, i, pulses_per_mm): if i >= self._iterations_a: return self._dir_a, None - a, direction, side = self.__circular_helper(self._start_a_pulses, i + 1, - self._radius_a_pulses, - self._side_a, self._dir_a) + a, direction, side = \ + self.__circular_helper(self._start_a_pulses, i + 1, + self._radius_a_pulses, + self._side_a, self._dir_a) a /= pulses_per_mm # first and last item can be slightly out of bound due float precision if i + 1 == self._iterations_a: @@ -624,9 +625,10 @@ class PulseGeneratorCircular(PulseGenerator): def __circular_b(self, i, pulses_per_mm): if i >= self._iterations_b: return self._dir_b, None - b, direction, side = self.__circular_helper(self._start_b_pulses, i + 1, - self._radius_b_pulses, - self._side_b, self._dir_b) + b, direction, side = \ + self.__circular_helper(self._start_b_pulses, i + 1, + self._radius_b_pulses, + self._side_b, self._dir_b) b /= pulses_per_mm # first and last item can be slightly out of bound due float precision if i + 1 == self._iterations_b: diff --git a/tests/test_gmachine.py b/tests/test_gmachine.py index 578c86d..901ec56 100644 --- a/tests/test_gmachine.py +++ b/tests/test_gmachine.py @@ -78,7 +78,8 @@ class TestGMachine(unittest.TestCase): self.assertRaises(GMachineException, m.do_command, GCode.parse_line("G1X1F-1")) cl = "G1X1F" + str(MIN_VELOCITY_MM_PER_MIN - 0.0000001) - self.assertRaises(GMachineException, m.do_command, GCode.parse_line(cl)) + self.assertRaises(GMachineException, m.do_command, + GCode.parse_line(cl)) m.do_command(GCode.parse_line("G1X100F" + str(MAX_VELOCITY_MM_PER_MIN_X))) m.do_command(GCode.parse_line("G1Y100F" @@ -120,7 +121,8 @@ class TestGMachine(unittest.TestCase): m.do_command, GCode.parse_line("G2X99999999Y99999999" "I1J1")) self.assertRaises(GMachineException, - m.do_command, GCode.parse_line("G2X2Y2Z99999999I1J1")) + m.do_command, + GCode.parse_line("G2X2Y2Z99999999I1J1")) self.assertEqual(m.position(), Coordinates(0, 0, 0, 0)) self.assertRaises(GMachineException, m.do_command, GCode.parse_line("G2X4Y4I2J2")) @@ -250,9 +252,9 @@ class TestGMachine(unittest.TestCase): self.assertEqual(m.extruder_target_temperature(), 0) self.assertRaises(GMachineException, m.do_command, GCode.parse_line("M104S"+str(MIN_TEMPERATURE - 1))) + et = EXTRUDER_MAX_TEMPERATURE + 1 self.assertRaises(GMachineException, m.do_command, - GCode.parse_line("M109S" - + str(EXTRUDER_MAX_TEMPERATURE + 1))) + GCode.parse_line("M109S" + str(et))) self.assertRaises(GMachineException, m.do_command, GCode.parse_line("M109")) diff --git a/tests/test_heater.py b/tests/test_heater.py index b52c6c7..ecdcc4d 100644 --- a/tests/test_heater.py +++ b/tests/test_heater.py @@ -64,8 +64,8 @@ class TestHeater(unittest.TestCase): def test_fail(self): # check if heater will not fix with incorrect temperature self._control_counter = 0 - he = Heater(self._target_temp, EXTRUDER_PID, self.__get_bad_temperature, - self.__control) + he = Heater(self._target_temp, EXTRUDER_PID, + self.__get_bad_temperature, self.__control) j = 0 while self._control_counter < 10: time.sleep(0.01) diff --git a/utils/heater_model_finder.py b/utils/heater_model_finder.py index e39058e..5cb6e9f 100755 --- a/utils/heater_model_finder.py +++ b/utils/heater_model_finder.py @@ -10,8 +10,8 @@ from cnc.hal_raspberry import hal """ -This executable module is looking for heating and cooling transfer coefficients. -Can be ran only on real hardware. +This executable module is looking for heating and cooling transfer +coefficients. Can be ran only on real hardware. """ # change settings below for your hardware/environment