mirror of
https://github.com/sinseman44/PyCNC.git
synced 2026-07-16 08:37:09 +00:00
spindle implementation
This commit is contained in:
+3
-1
@@ -34,8 +34,9 @@ class GMachine(object):
|
||||
def release(self):
|
||||
""" Return machine to original position and free all resources.
|
||||
"""
|
||||
self._spindle(0)
|
||||
self.home()
|
||||
hal.join()
|
||||
hal.deinit()
|
||||
|
||||
def reset(self):
|
||||
""" Reinitialize all program configurable thing.
|
||||
@@ -48,6 +49,7 @@ class GMachine(object):
|
||||
self._absoluteCoordinates = True
|
||||
|
||||
def _spindle(self, spindle_speed):
|
||||
hal.join()
|
||||
hal.spindle_control(100.0 * spindle_speed / SPINDLE_MAX_RPM)
|
||||
|
||||
def _move(self, delta, velocity):
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
# """ Wait till motors work.
|
||||
# """
|
||||
# do_something()
|
||||
#
|
||||
# def deinit():
|
||||
# """ De-initialise hal, stop any hardware.
|
||||
# """
|
||||
# do_something()
|
||||
|
||||
|
||||
# check which module to import
|
||||
@@ -48,4 +53,6 @@ if 'move_linear' not in locals():
|
||||
raise NotImplementedError("hal.move_linear() not implemented")
|
||||
if 'join' not in locals():
|
||||
raise NotImplementedError("hal.join() not implemented")
|
||||
if 'deinit' not in locals():
|
||||
raise NotImplementedError("hal.deinit() not implemented")
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ US_IN_SECONDS = 1000000
|
||||
|
||||
gpio = rpgpio.GPIO()
|
||||
dma = rpgpio.DMAGPIO()
|
||||
pwm = rpgpio.DMAPWM()
|
||||
|
||||
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
|
||||
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
|
||||
@@ -40,6 +41,8 @@ def init():
|
||||
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
|
||||
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
|
||||
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
|
||||
gpio.init(SPINDLE_PWM_PIN, rpgpio.GPIO.MODE_OUTPUT)
|
||||
gpio.clear(SPINDLE_PWM_PIN)
|
||||
|
||||
# calibration
|
||||
gpio.set(STEPPER_DIR_PIN_X)
|
||||
@@ -71,10 +74,13 @@ def init():
|
||||
|
||||
def spindle_control(percent):
|
||||
""" Spindle control implementation.
|
||||
:param percent: Spindle speed in percent.
|
||||
:param percent: Spindle speed in percent. If 0, stop the spindle.
|
||||
"""
|
||||
# TODO spindle control.
|
||||
logging.info("TODO spindle control: {}%".format(percent))
|
||||
logging.info("spindle control: {}%".format(percent))
|
||||
if percent > 0:
|
||||
pwm.add_pin(SPINDLE_PWM_PIN, percent)
|
||||
else:
|
||||
pwm.remove_pin(SPINDLE_PWM_PIN)
|
||||
|
||||
|
||||
def move_linear(delta, velocity):
|
||||
@@ -147,6 +153,14 @@ def move_linear(delta, velocity):
|
||||
def join():
|
||||
""" Wait till motors work.
|
||||
"""
|
||||
logging.info("hal join()")
|
||||
# wait till dma works
|
||||
while dma.is_active():
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
def deinit():
|
||||
""" De-initialize hardware.
|
||||
"""
|
||||
join()
|
||||
pwm.remove_all()
|
||||
|
||||
+127
-39
@@ -78,10 +78,12 @@ class GPIO(object):
|
||||
return 1
|
||||
|
||||
|
||||
class DMAGPIO(object):
|
||||
# When DMAGPIO is an active with two channels simultaneously, delay time shifts
|
||||
# a little bit, because all DMA channels query the same PWM(which is used as
|
||||
# clock for delay). So, do not create two or more instances of DMAGPIO.
|
||||
class DMAGPIO(DMAProto):
|
||||
_DMA_CONTROL_BLOCK_SIZE = 32
|
||||
_DMA_CHANNEL = 5
|
||||
|
||||
def __init__(self):
|
||||
""" Create object which control GPIO pins via DMA(Direct Memory
|
||||
Access).
|
||||
@@ -92,12 +94,11 @@ class DMAGPIO(object):
|
||||
otherwise memory will be unlocked and it could be overwritten by
|
||||
operating system.
|
||||
"""
|
||||
# allocate buffer for control blocks, always 32 MB
|
||||
self._physmem = CMAPhysicalMemory(32 * 1024 * 1024)
|
||||
super(DMAGPIO, self).__init__(31 * 1024 * 1024)
|
||||
self.__current_address = 0
|
||||
|
||||
# prepare dma registers memory map
|
||||
self._dma = PhysicalMemory(PERI_BASE + DMA_BASE)
|
||||
# get helpers registers, this class uses PWM module to create precise
|
||||
# delays
|
||||
self._pwm = PhysicalMemory(PERI_BASE + PWM_BASE)
|
||||
self._clock = PhysicalMemory(PERI_BASE + CM_BASE)
|
||||
|
||||
@@ -119,7 +120,10 @@ class DMAGPIO(object):
|
||||
|
||||
def add_pulse(self, pins_mask, length_us):
|
||||
""" Add single pulse at the current position.
|
||||
:param pins_mask: bitwise mask of GPIO pins to trigger. Only for first 32 pins.
|
||||
Note: GPIO pins are not initialized in this method and should be
|
||||
initialized in advance before running.
|
||||
:param pins_mask: bitwise mask of GPIO pins to trigger. Only for
|
||||
first 32 pins.
|
||||
:param length_us: length in us.
|
||||
"""
|
||||
next_cb = self.__current_address + 3 * self._DMA_CONTROL_BLOCK_SIZE
|
||||
@@ -187,16 +191,7 @@ class DMAGPIO(object):
|
||||
| PWM_DMAC_PANIC(15) | PWM_DMAC_DREQ(15))
|
||||
self._pwm.write_int(PWM_CTL, PWM_CTL_CLRF)
|
||||
self._pwm.write_int(PWM_CTL, PWM_CTL_USEF1 | PWM_CTL_PWEN1)
|
||||
# configure DMA
|
||||
addr = 0x100 * self._DMA_CHANNEL
|
||||
cs = self._dma.read_int(addr)
|
||||
cs |= DMA_CS_END
|
||||
self._dma.write_int(addr, cs)
|
||||
self._dma.write_int(addr + 4, self._physmem.get_bus_address())
|
||||
cs = DMA_CS_PRIORITY(7) | DMA_CS_PANIC_PRIORITY(7) | DMA_CS_DISDEBUG
|
||||
self._dma.write_int(addr, cs)
|
||||
cs |= DMA_CS_ACTIVE
|
||||
self._dma.write_int(addr, cs)
|
||||
super(DMAGPIO, self)._run_dma()
|
||||
|
||||
def run(self, loop=False):
|
||||
""" Run DMA module and start sending specified pulses.
|
||||
@@ -217,32 +212,115 @@ class DMAGPIO(object):
|
||||
""" Stop any DMA activities.
|
||||
"""
|
||||
self._pwm.write_int(PWM_CTL, 0)
|
||||
addr = 0x100 * self._DMA_CHANNEL
|
||||
cs = self._dma.read_int(addr)
|
||||
cs |= DMA_CS_ABORT
|
||||
self._dma.write_int(addr, cs)
|
||||
cs &= ~DMA_CS_ACTIVE
|
||||
self._dma.write_int(addr, cs)
|
||||
cs |= DMA_CS_RESET
|
||||
self._dma.write_int(addr, cs)
|
||||
|
||||
def is_active(self):
|
||||
""" Check if DMA is working. Method can check if single sent sequence
|
||||
still active.
|
||||
:return: boolean value
|
||||
"""
|
||||
addr = 0x100 * self._DMA_CHANNEL
|
||||
cs = self._dma.read_int(addr)
|
||||
if cs & DMA_CS_ACTIVE == DMA_CS_ACTIVE:
|
||||
return True
|
||||
return False
|
||||
super(DMAGPIO, self)._stop_dma()
|
||||
|
||||
def clear(self):
|
||||
""" Remove any specified pulses.
|
||||
""" Remove any specified pulses. Doesn't affect currently running
|
||||
sequence.
|
||||
"""
|
||||
self.__current_address = 0
|
||||
|
||||
|
||||
class DMAPWM(DMAProto):
|
||||
_DMA_CHANNEL = 14
|
||||
_DMA_CONTROL_BLOCK_SIZE = 32
|
||||
_DMA_DATA_OFFSET = 24
|
||||
_TOTAL_NUMBER_OF_BLOCKS = 256
|
||||
def __init__(self):
|
||||
""" Initialise PWM. PWM has 8 bit resolution and fixed frequency
|
||||
(~11.5 KHz and may flow). Though duty cycle is quite precise and
|
||||
it uses the minimum amount of system resources (just one lite DMA
|
||||
channel without any anything else).
|
||||
That's why such PWM is best to use with collector motors, heaters
|
||||
and other non sensitive hardware.
|
||||
Implementation is super simple and uses lite DMA channel.
|
||||
Overall frequency depends on number of blocks.
|
||||
To adjust frequency, just write more byte per operation, use Wait
|
||||
Cycles in info field of control blocks.
|
||||
"""
|
||||
super(DMAPWM, self).__init__(self._TOTAL_NUMBER_OF_BLOCKS
|
||||
* self._DMA_CONTROL_BLOCK_SIZE)
|
||||
self._clear_pins = dict()
|
||||
# first control block always set pins
|
||||
self.__add_control_block(0, GPIO_SET_OFFSET)
|
||||
# fill control blocks
|
||||
for i in range(1, self._TOTAL_NUMBER_OF_BLOCKS):
|
||||
self.__add_control_block(i * self._DMA_CONTROL_BLOCK_SIZE,
|
||||
GPIO_CLEAR_OFFSET)
|
||||
# loop
|
||||
self._physmem.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1)
|
||||
* self._DMA_CONTROL_BLOCK_SIZE + 20,
|
||||
self._physmem.get_bus_address())
|
||||
self._gpio = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
|
||||
|
||||
def __add_control_block(self, address, offset):
|
||||
ba = self._physmem.get_bus_address() + address
|
||||
data = (
|
||||
DMA_TI_NO_WIDE_BURSTS | DMA_TI_WAIT_RESP
|
||||
| DMA_TI_DEST_INC | DMA_TI_SRC_INC, # info
|
||||
ba + self._DMA_DATA_OFFSET, # source, last 8 bytes are padding, use it to store data
|
||||
PHYSICAL_GPIO_BUS + offset, # destination
|
||||
4, # length
|
||||
0, # stride
|
||||
ba + self._DMA_CONTROL_BLOCK_SIZE, # next control block
|
||||
0, # padding, uses as data storage
|
||||
0 # padding
|
||||
)
|
||||
self._physmem.write(address, data)
|
||||
|
||||
def add_pin(self, pin, duty_cycle):
|
||||
""" Add pin to PMW with specified duty cycle.
|
||||
:param pin: pin number to add.
|
||||
:param duty_cycle: duty cycle 0..100 which represent percents.
|
||||
"""
|
||||
assert 0 <= duty_cycle <= 100
|
||||
self.remove_pin(pin)
|
||||
block_number = int(duty_cycle * self._TOTAL_NUMBER_OF_BLOCKS
|
||||
/ 100.0)
|
||||
if block_number == 0:
|
||||
self._gpio.write_int(GPIO_CLEAR_OFFSET, 1 << pin)
|
||||
elif block_number == self._TOTAL_NUMBER_OF_BLOCKS:
|
||||
self._gpio.write_int(GPIO_SET_OFFSET, 1 << pin)
|
||||
self._clear_pins[pin] = self._DMA_DATA_OFFSET
|
||||
else:
|
||||
value = self._physmem.read_int(self._DMA_DATA_OFFSET)
|
||||
value |= 1 << pin
|
||||
self._physmem.write_int(self._DMA_DATA_OFFSET, value)
|
||||
clear_address = block_number * self._DMA_CONTROL_BLOCK_SIZE \
|
||||
+ self._DMA_DATA_OFFSET
|
||||
value = self._physmem.read_int(clear_address)
|
||||
value |= 1 << pin
|
||||
self._physmem.write_int(clear_address, value)
|
||||
self._clear_pins[pin] = clear_address
|
||||
if not self.is_active():
|
||||
super(DMAPWM, self)._run_dma()
|
||||
|
||||
def remove_pin(self, pin):
|
||||
""" Remove pin from PWM
|
||||
:param pin: pin number to remove.
|
||||
"""
|
||||
assert 0 <= pin < 32
|
||||
if pin in self._clear_pins.keys():
|
||||
address = self._clear_pins[pin]
|
||||
value = self._physmem.read_int(address)
|
||||
value &= ~(1 << pin)
|
||||
self._physmem.write_int(address, value)
|
||||
value = self._physmem.read_int(self._DMA_DATA_OFFSET)
|
||||
value &= ~(1 << pin)
|
||||
self._physmem.write_int(self._DMA_DATA_OFFSET, value)
|
||||
del self._clear_pins[pin]
|
||||
self._gpio.write_int(GPIO_CLEAR_OFFSET, 1 << pin)
|
||||
if len(self._clear_pins) == 0 and self.is_active():
|
||||
super(DMAPWM, self)._stop_dma()
|
||||
|
||||
def remove_all(self):
|
||||
""" Remove all pins from PWM and stop it.
|
||||
"""
|
||||
pins_list = self._clear_pins.keys()
|
||||
for pin in pins_list:
|
||||
self.remove_pin(pin)
|
||||
assert len(self._clear_pins) == 0
|
||||
|
||||
# for testing purpose
|
||||
def main():
|
||||
pin = 21
|
||||
@@ -272,8 +350,8 @@ def main():
|
||||
print("now " + hex(a))
|
||||
del cma
|
||||
dg = DMAGPIO()
|
||||
dg.add_pulse(1 << pin, 4000)
|
||||
dg.add_delay(12000)
|
||||
dg.add_pulse(1 << pin, 100000)
|
||||
dg.add_delay(600000)
|
||||
dg.run(True)
|
||||
print("dmagpio is started")
|
||||
try:
|
||||
@@ -284,6 +362,16 @@ def main():
|
||||
dg.stop()
|
||||
g.clear(pin)
|
||||
print("dma stopped")
|
||||
pwm = DMAPWM()
|
||||
pwm.add_pin(pin, 20)
|
||||
print("pwm is started")
|
||||
try:
|
||||
print("press enter to stop...")
|
||||
sys.stdin.readline()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
pwm.remove_pin(pin)
|
||||
print("pwm stopped")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -175,6 +175,8 @@ class CMAPhysicalMemory(PhysicalMemory):
|
||||
atexit.register(self.free)
|
||||
|
||||
def free(self):
|
||||
"""Release and free allocated memory
|
||||
"""
|
||||
self._send_data(0x3000e, [self._handle]) # unlock memory
|
||||
self._send_data(0x3000f, [self._handle]) # free memory
|
||||
self._close_dev(self._vcio_fd)
|
||||
@@ -196,3 +198,50 @@ class CMAPhysicalMemory(PhysicalMemory):
|
||||
|
||||
def get_phys_address(self):
|
||||
return self._busmem & ~0xc0000000
|
||||
|
||||
|
||||
class DMAProto(object):
|
||||
def __init__(self, memory_size):
|
||||
""" This class provides basic access to DMA and creates buffer for
|
||||
control blocks.
|
||||
"""
|
||||
# allocate buffer for control blocks
|
||||
self._physmem = CMAPhysicalMemory(memory_size)
|
||||
# prepare dma registers memory map
|
||||
self._dma = PhysicalMemory(PERI_BASE + DMA_BASE)
|
||||
|
||||
def _run_dma(self):
|
||||
""" Run DMA module from created buffer.
|
||||
"""
|
||||
address = 0x100 * self._DMA_CHANNEL
|
||||
cs = self._dma.read_int(address)
|
||||
cs |= DMA_CS_END
|
||||
self._dma.write_int(address, cs)
|
||||
self._dma.write_int(address + 4, self._physmem.get_bus_address())
|
||||
cs = DMA_CS_PRIORITY(7) | DMA_CS_PANIC_PRIORITY(7) | DMA_CS_DISDEBUG
|
||||
self._dma.write_int(address, cs)
|
||||
cs |= DMA_CS_ACTIVE
|
||||
self._dma.write_int(address, cs)
|
||||
|
||||
def _stop_dma(self):
|
||||
""" Stop DMA
|
||||
"""
|
||||
address = 0x100 * self._DMA_CHANNEL
|
||||
cs = self._dma.read_int(address)
|
||||
cs |= DMA_CS_ABORT
|
||||
self._dma.write_int(address, cs)
|
||||
cs &= ~DMA_CS_ACTIVE
|
||||
self._dma.write_int(address, cs)
|
||||
cs |= DMA_CS_RESET
|
||||
self._dma.write_int(address, cs)
|
||||
|
||||
def is_active(self):
|
||||
""" Check if DMA is working. Method can check if single sequence
|
||||
still active or cycle sequence is working.
|
||||
:return: boolean value
|
||||
"""
|
||||
address = 0x100 * self._DMA_CHANNEL
|
||||
cs = self._dma.read_int(address)
|
||||
if cs & DMA_CS_ACTIVE == DMA_CS_ACTIVE:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -89,3 +89,9 @@ def join():
|
||||
""" Wait till motors work.
|
||||
"""
|
||||
logging.info("hal join()")
|
||||
|
||||
|
||||
def deinit():
|
||||
""" De-initialise.
|
||||
"""
|
||||
logging.info("hal deinit()")
|
||||
|
||||
Reference in New Issue
Block a user