diff --git a/cnc/hal_raspberry/hal.py b/cnc/hal_raspberry/hal.py index 345de6d..b32b54c 100644 --- a/cnc/hal_raspberry/hal.py +++ b/cnc/hal_raspberry/hal.py @@ -175,17 +175,32 @@ def move(generator): """ Move head to specified position :param generator: PulseGenerator object. """ - # wait if previous command still works - while dma.is_active(): - time.sleep(0.001) + # Fill buffer right before currently running(previous sequence) dma + # this mode implements kind of round buffer, but protects if CPU is not + # powerful enough to calculate buffer in advance, faster then machine + # moving. In this case machine would safely paused between commands until + # calculation is done. + # 4 control blocks per 32 bytes + bytes_per_iter = 4 * dma.control_block_size() # prepare and run dma - dma.clear() + dma.clear() # should just clear current address, but not stop current DMA prev = 0 is_ran = False instant = INSTANT_RUN st = time.time() + current_cb = 0 + k = 0 + k0 = 0 for direction, tx, ty, tz, te in generator: + if current_cb is not None: + while dma.current_address() + bytes_per_iter >= current_cb: + time.sleep(0.001) + current_cb = dma.current_control_block() + if current_cb is None: + k0 = k + st = time.time() + break # previous dma sequence has stopped if direction: # set up directions pins_to_set = 0 pins_to_clear = 0 @@ -229,19 +244,27 @@ def move(generator): # matter for pulses with 1-2us length. prev = k + STEPPER_PULSE_LENGTH_US # instant run handling - if not is_ran and instant: - if k > 500000: # wait at least 500 ms is uploaded - if time.time() - st > 0.5: + if not is_ran and instant and current_cb is None: + if k - k0 > 100000: # wait at least 100 ms is uploaded + nt = time.time() - st + ng = (k - k0)/ 1000000.0 + if nt > ng: logging.warn("Buffer preparing for instant run took more " - "time then buffer time") + "time then buffer time" + " {}/{}".format(nt, ng)) instant = False else: dma.run_stream() is_ran = True pt = time.time() if not is_ran: + # after long command, we can fill short buffer, that why we may need to + # wait until long command finishes + while dma.is_active(): + time.sleep(0.01) dma.run(False) else: + # stream mode can be activated only if previous command was finished. dma.finalize_stream() logging.info("prepared in " + str(round(pt - st, 2)) + "s, estimated in " diff --git a/cnc/hal_raspberry/rpgpio.py b/cnc/hal_raspberry/rpgpio.py index 3718985..b9ed677 100755 --- a/cnc/hal_raspberry/rpgpio.py +++ b/cnc/hal_raspberry/rpgpio.py @@ -241,6 +241,18 @@ class DMAGPIO(DMAProto): """ self.__current_address = 0 + def current_address(self): + """ Get current buffer offset. + :return: current buffer offset in bytes. + """ + return self.__current_address + + def control_block_size(self): + """ Get control block size. + :return: control block size in bytes. + """ + return self._DMA_CONTROL_BLOCK_SIZE + class DMAPWM(DMAProto): _DMA_CONTROL_BLOCK_SIZE = 32 diff --git a/cnc/hal_raspberry/rpgpio_private.py b/cnc/hal_raspberry/rpgpio_private.py index 9264e65..84bf514 100644 --- a/cnc/hal_raspberry/rpgpio_private.py +++ b/cnc/hal_raspberry/rpgpio_private.py @@ -198,7 +198,7 @@ class DMAProto(object): """ This class provides basic access to DMA and creates buffer for control blocks. """ - self._DMA_CHANNEL = dma_channel + self._DMA_CHANNEL_ADDRESS = 0x100 * dma_channel # allocate buffer for control blocks self._phys_memory = CMAPhysicalMemory(memory_size) # prepare dma registers memory map @@ -207,34 +207,41 @@ class DMAProto(object): def _run_dma(self): """ Run DMA module from created buffer. """ - address = 0x100 * self._DMA_CHANNEL - self._dma.write_int(address + DMA_CS, DMA_CS_END) - self._dma.write_int(address + DMA_CONBLK_AD, + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, DMA_CS_END) + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CONBLK_AD, self._phys_memory.get_bus_address()) cs = DMA_CS_PRIORITY(7) | DMA_CS_PANIC_PRIORITY(7) | DMA_CS_DISDEBUG - self._dma.write_int(address + DMA_CS, cs) + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) cs |= DMA_CS_ACTIVE - self._dma.write_int(address + DMA_CS, cs) + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) def _stop_dma(self): """ Stop DMA """ - address = 0x100 * self._DMA_CHANNEL - cs = self._dma.read_int(address + DMA_CS) + cs = self._dma.read_int(self._DMA_CHANNEL_ADDRESS + DMA_CS) cs |= DMA_CS_ABORT - self._dma.write_int(address + DMA_CS, cs) + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) cs &= ~DMA_CS_ACTIVE - self._dma.write_int(address + DMA_CS, cs) + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) cs |= DMA_CS_RESET - self._dma.write_int(address + DMA_CS, cs) + self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, 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 + DMA_CS) + cs = self._dma.read_int(self._DMA_CHANNEL_ADDRESS + DMA_CS) if cs & DMA_CS_ACTIVE == DMA_CS_ACTIVE: return True return False + + def current_control_block(self): + """ Get current dma control block address. + :return: Currently running DMA control block offset in bytes or None + value if DMA is not running. + """ + cb = self._dma.read_int(self._DMA_CHANNEL_ADDRESS + DMA_CONBLK_AD) + if cb == 0: + return None + return cb - self._phys_memory.get_bus_address()