mirror of
https://github.com/sinseman44/PyCNC.git
synced 2026-07-16 08:37:09 +00:00
reorganise project
This commit is contained in:
Executable
@@ -0,0 +1,152 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
import rpgpio
|
||||
|
||||
from cnc.pulses import PulseGeneratorLinear
|
||||
from cnc.coordinates import Coordinates
|
||||
from cnc.config import *
|
||||
|
||||
# Stepper motors channel for RPIO
|
||||
STEPPER_CHANNEL = 0
|
||||
# Since there is no way to add pulses and then start cycle in RPIO,
|
||||
# use this delay to start adding pulses to cycle. It can be easily
|
||||
# solved by modifying RPIO in a way of adding method to start cycle
|
||||
# explicitly.
|
||||
RPIO_START_DELAY_US = 200000
|
||||
# Since RPIO generate cycles in loop, use this delay to stop RPIO
|
||||
# It can be removed if RPIO would allow to run single shot cycle.
|
||||
RPIO_STOP_DELAY_US = 5000000
|
||||
|
||||
US_IN_SECONDS = 1000000
|
||||
|
||||
gpio = rpgpio.GPIO()
|
||||
dma = rpgpio.DMAGPIO()
|
||||
|
||||
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
|
||||
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
|
||||
STEP_PIN_MASK_Z = 1 << STEPPER_STEP_PIN_Z
|
||||
|
||||
def init():
|
||||
""" Initialize GPIO pins and machine itself, including callibration if
|
||||
needed. Do not return till all procedure is completed.
|
||||
"""
|
||||
gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
|
||||
gpio.init(STEPPER_STEP_PIN_Y, rpgpio.GPIO.MODE_OUTPUT)
|
||||
gpio.init(STEPPER_STEP_PIN_Z, rpgpio.GPIO.MODE_OUTPUT)
|
||||
gpio.init(STEPPER_DIR_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
|
||||
gpio.init(STEPPER_DIR_PIN_Y, rpgpio.GPIO.MODE_OUTPUT)
|
||||
gpio.init(STEPPER_DIR_PIN_Z, rpgpio.GPIO.MODE_OUTPUT)
|
||||
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)
|
||||
|
||||
# calibration
|
||||
gpio.set(STEPPER_DIR_PIN_X)
|
||||
gpio.set(STEPPER_DIR_PIN_Y)
|
||||
gpio.set(STEPPER_DIR_PIN_Z)
|
||||
pins = STEP_PIN_MASK_X | STEP_PIN_MASK_Y | STEP_PIN_MASK_Z
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
while True:
|
||||
if (STEP_PIN_MASK_X & pins) != 0 and gpio.read(ENDSTOP_PIN_X) == 0:
|
||||
pins &= ~STEP_PIN_MASK_X
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
if (STEP_PIN_MASK_Y & pins) != 0 and gpio.read(ENDSTOP_PIN_Y) == 0:
|
||||
pins &= ~STEP_PIN_MASK_Y
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
if (STEP_PIN_MASK_Z & pins) != 0 and gpio.read(ENDSTOP_PIN_Z) == 0:
|
||||
pins &= ~STEP_PIN_MASK_Z
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
if pins == 0:
|
||||
break
|
||||
dma.run(False)
|
||||
# limit velocity at ~10% of top velocity
|
||||
time.sleep((1 / 0.10) / (STEPPER_MAX_VELOCITY_MM_PER_MIN
|
||||
/ 60 * STEPPER_PULSES_PER_MM))
|
||||
|
||||
|
||||
def spindle_control(percent):
|
||||
""" Spindle control implementation.
|
||||
:param percent: Spindle speed in percent.
|
||||
"""
|
||||
# TODO spindle control.
|
||||
logging.info("TODO spindle control: {}%".format(percent))
|
||||
|
||||
|
||||
def move_linear(delta, velocity):
|
||||
""" Move head to specified position
|
||||
:param delta: Coordinated object, delta position in mm
|
||||
:param velocity: velocity in mm per min
|
||||
"""
|
||||
logging.info("move {} with velocity {}".format(delta, velocity))
|
||||
# initialize generator
|
||||
generator = PulseGeneratorLinear(delta, velocity)
|
||||
# wait if previous command still works
|
||||
while dma.is_active():
|
||||
time.sleep(0.001)
|
||||
|
||||
# set direction pins
|
||||
if delta.x > 0.0:
|
||||
gpio.clear(STEPPER_DIR_PIN_X)
|
||||
else:
|
||||
gpio.set(STEPPER_DIR_PIN_X)
|
||||
if delta.y > 0.0:
|
||||
gpio.clear(STEPPER_DIR_PIN_Y)
|
||||
else:
|
||||
gpio.set(STEPPER_DIR_PIN_Y)
|
||||
if delta.z > 0.0:
|
||||
gpio.clear(STEPPER_DIR_PIN_Z)
|
||||
else:
|
||||
gpio.set(STEPPER_DIR_PIN_Z)
|
||||
|
||||
# prepare and run dma
|
||||
dma.clear()
|
||||
prev = 0
|
||||
is_ran = False
|
||||
st = time.time()
|
||||
for tx, ty, tz in generator:
|
||||
pins = 0
|
||||
k = int(round(min(x for x in (tx, ty, tz) if x is not None)
|
||||
* US_IN_SECONDS))
|
||||
if tx is not None:
|
||||
pins |= STEP_PIN_MASK_X
|
||||
if ty is not None:
|
||||
pins |= STEP_PIN_MASK_Y
|
||||
if tz is not None:
|
||||
pins |= STEP_PIN_MASK_Z
|
||||
if k - prev > 0:
|
||||
dma.add_delay(k - prev)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
# TODO not a precise way! pulses will set in queue, instead of crossing
|
||||
# if next pulse start during pulse length. Though it almost doesn't
|
||||
# matter for pulses with 1-2us length.
|
||||
prev = k + STEPPER_PULSE_LINGTH_US
|
||||
# instant run handling
|
||||
if not is_ran and INSTANT_RUN:
|
||||
if k > 500000: # wait at least 500 ms is uploaded
|
||||
if time.time() - st > 0.5:
|
||||
# may be instant run should be canceled here?
|
||||
logging.warn("Buffer preparing for instant run took more "
|
||||
"time then buffer time")
|
||||
dma.run_stream()
|
||||
is_ran = True
|
||||
pt = time.time()
|
||||
if not is_ran:
|
||||
dma.run(False)
|
||||
else:
|
||||
dma.finalize_stream()
|
||||
|
||||
logging.info("prepared in " + str(round(pt - st, 2)) + "s, estimated in "
|
||||
+ str(round(generator.total_time_s(), 2)) + "s")
|
||||
|
||||
|
||||
def join():
|
||||
""" Wait till motors work.
|
||||
"""
|
||||
# wait till dma works
|
||||
while dma.is_active():
|
||||
time.sleep(0.01)
|
||||
Executable
+289
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from rpgpio_private import *
|
||||
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import struct
|
||||
|
||||
|
||||
class GPIO(object):
|
||||
MODE_OUTPUT = 1
|
||||
MODE_INPUT_NOPULL = 2
|
||||
MODE_INPUT_PULLUP = 3
|
||||
MODE_INPUT_PULLDOWN = 4
|
||||
|
||||
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.
|
||||
"""
|
||||
self._mem = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
|
||||
|
||||
def _pullupdn(self, pin, mode):
|
||||
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
|
||||
p &= ~3
|
||||
if mode == self.MODE_INPUT_PULLUP:
|
||||
p |= 2
|
||||
elif mode == self.MODE_INPUT_PULLDOWN:
|
||||
p |= 1
|
||||
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
|
||||
addr = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET
|
||||
self._mem.write_int(addr, 1 << (pin % 32))
|
||||
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
|
||||
p &= ~3
|
||||
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
|
||||
self._mem.write_int(addr, 0)
|
||||
|
||||
def init(self, pin, mode):
|
||||
""" Initialize or re-initialize GPIO pin.
|
||||
:param pin: pin number.
|
||||
:param mode: one of MODE_* variables in this class.
|
||||
"""
|
||||
addr = 4 * int(pin / 10) + GPIO_FSEL_OFFSET
|
||||
v = self._mem.read_int(addr)
|
||||
v &= ~(7 << ((pin % 10) * 3)) # input value
|
||||
if mode == self.MODE_OUTPUT:
|
||||
v |= (1 << ((pin % 10) * 3)) # output value, base on input
|
||||
self._mem.write_int(addr, v)
|
||||
else:
|
||||
self._mem.write_int(addr, v)
|
||||
self._pullupdn(pin, mode)
|
||||
|
||||
def set(self, pin):
|
||||
""" Set pin to HIGH state.
|
||||
:param pin: pin number.
|
||||
"""
|
||||
addr = 4 * int(pin / 32) + GPIO_SET_OFFSET
|
||||
self._mem.write_int(addr, 1 << (pin % 32))
|
||||
|
||||
def clear(self, pin):
|
||||
""" Set pin to LOW state.
|
||||
:param pin: pin number.
|
||||
"""
|
||||
addr = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET
|
||||
self._mem.write_int(addr, 1 << (pin % 32))
|
||||
|
||||
def read(self, pin):
|
||||
""" Read pin current value.
|
||||
:param pin: pin number.
|
||||
:return: integer value 0 or 1.
|
||||
"""
|
||||
addr = 4 * int(pin / 32) + GPIO_INPUT_OFFSET
|
||||
v = self._mem.read_int(addr)
|
||||
v &= 1 << (pin % 32)
|
||||
if v == 0:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
class DMAGPIO(object):
|
||||
_DMA_CONTROL_BLOCK_SIZE = 32
|
||||
_DMA_CHANNEL = 5
|
||||
|
||||
def __init__(self):
|
||||
""" Create object which control GPIO pins via DMA(Direct Memory
|
||||
Access).
|
||||
This object allows to add arbitrary sequence of pulses to any GPIO
|
||||
outputs and run this sequence in background without using CPU since
|
||||
DMA is a separated hardware module.
|
||||
Note: keep this object out of garbage collector until it stops,
|
||||
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)
|
||||
self.__current_address = 0
|
||||
|
||||
# prepare dma registers memory map
|
||||
self._dma = PhysicalMemory(PERI_BASE + DMA_BASE)
|
||||
self._pwm = PhysicalMemory(PERI_BASE + PWM_BASE)
|
||||
self._clock = PhysicalMemory(PERI_BASE + CM_BASE)
|
||||
|
||||
# pre calculated variables for control blocks
|
||||
self._delay_info = DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE \
|
||||
| DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM) \
|
||||
| DMA_TI_DEST_DREQ
|
||||
self._delay_destination = PHYSICAL_PWM_BUS + 0x18
|
||||
self._delay_stride = 0
|
||||
|
||||
self._pulse_info = DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE \
|
||||
| DMA_TI_WAIT_RESP
|
||||
self._pulse_destination = PHYSICAL_GPIO_BUS + GPIO_SET_OFFSET
|
||||
# YLENGTH is transfers count and XLENGTH size of each transfer
|
||||
self._pulse_length = DMA_TI_TXFR_LEN_YLENGTH(2) \
|
||||
| DMA_TI_TXFR_LEN_XLENGTH(4)
|
||||
self._pulse_stride = DMA_TI_STRIDE_D_STRIDE(12) \
|
||||
| DMA_TI_STRIDE_S_STRIDE(4)
|
||||
|
||||
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.
|
||||
:param length_us: length in us.
|
||||
"""
|
||||
next_cb = self.__current_address + 3 * self._DMA_CONTROL_BLOCK_SIZE
|
||||
if next_cb > self._physmem.get_size():
|
||||
raise MemoryError("Out of allocated memory.")
|
||||
next3 = next_cb + self._physmem.get_bus_address()
|
||||
next2 = next3 - self._DMA_CONTROL_BLOCK_SIZE
|
||||
next1 = next2 - self._DMA_CONTROL_BLOCK_SIZE
|
||||
|
||||
source1 = next1 - 8 # last 8 bytes are padding, use it to store data
|
||||
length2 = 16 * length_us
|
||||
source3 = next3 - 8
|
||||
|
||||
data = (
|
||||
self._pulse_info, source1, self._pulse_destination,
|
||||
self._pulse_length,
|
||||
self._pulse_stride, next1, pins_mask, 0,
|
||||
self._delay_info, 0, self._delay_destination, length2,
|
||||
self._delay_stride, next2, 0, 0,
|
||||
self._pulse_info, source3, self._pulse_destination,
|
||||
self._pulse_length,
|
||||
self._pulse_stride, next3, 0, pins_mask
|
||||
)
|
||||
self._physmem.write(self.__current_address, data)
|
||||
self.__current_address = next_cb
|
||||
|
||||
def add_delay(self, delay_us):
|
||||
""" Add delay at the current position.
|
||||
:param delay_us: delay in us.
|
||||
"""
|
||||
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE
|
||||
if next_cb > self._physmem.get_size():
|
||||
raise MemoryError("Out of allocated memory.")
|
||||
next = self._physmem.get_bus_address() + next_cb
|
||||
source = next - 8 # last 8 bytes are padding, use it to store data
|
||||
length = 16 * delay_us
|
||||
data = (
|
||||
self._delay_info, source, self._delay_destination, length,
|
||||
self._delay_stride, next, 0, 0
|
||||
)
|
||||
self._physmem.write(self.__current_address, data)
|
||||
self.__current_address = next_cb
|
||||
|
||||
def finalize_stream(self):
|
||||
""" Mark last added block as the last one.
|
||||
"""
|
||||
self._physmem.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)))
|
||||
|
||||
def run_stream(self):
|
||||
""" Run DMA module in stream mode, i.e. does'n finalize last block
|
||||
and do not check if there is anything to do.
|
||||
"""
|
||||
# configure PWM hardware module which will clocks DMA
|
||||
self._pwm.write_int(PWM_CTL, 0)
|
||||
self._clock.write_int(CM_CNTL, CM_PASSWORD | CM_SRC_PLLD) # disable
|
||||
while (self._clock.read_int(CM_CNTL) & (1 << 7)) != 0:
|
||||
time.sleep(0.00001) # 10 us, wait until BUSY bit is clear
|
||||
self._clock.write_int(CM_DIV, CM_PASSWORD | CM_DIV_VALUE(50)) # 10MHz
|
||||
self._clock.write_int(CM_CNTL, CM_PASSWORD | CM_SRC_PLLD | CM_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_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)
|
||||
|
||||
def run(self, loop=False):
|
||||
""" Run DMA module and start sending specified pulses.
|
||||
:param loop: If true, run pulse sequence in infinite loop. Otherwise
|
||||
"""
|
||||
if self.__current_address == 0:
|
||||
raise RuntimeError("Nothing was added.")
|
||||
# fix 'next' field in previous control block
|
||||
if loop:
|
||||
self._physmem.write_int(self.__current_address + 20
|
||||
- self._DMA_CONTROL_BLOCK_SIZE,
|
||||
self._physmem.get_bus_address())
|
||||
else:
|
||||
self.finalize_stream()
|
||||
self.run_stream()
|
||||
|
||||
def stop(self):
|
||||
""" 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
|
||||
|
||||
def clear(self):
|
||||
""" Remove any specified pulses.
|
||||
"""
|
||||
self.__current_address = 0
|
||||
|
||||
|
||||
# for testing purpose
|
||||
def main():
|
||||
pin = 21
|
||||
g = GPIO()
|
||||
g.init(pin, GPIO.MODE_INPUT_NOPULL)
|
||||
print("nopull " + str(g.read(pin)))
|
||||
g.init(pin, GPIO.MODE_INPUT_PULLDOWN)
|
||||
print("pulldown " + str(g.read(pin)))
|
||||
g.init(pin, GPIO.MODE_INPUT_PULLUP)
|
||||
print("pullup " + str(g.read(pin)))
|
||||
time.sleep(1)
|
||||
g.init(pin, GPIO.MODE_OUTPUT)
|
||||
g.set(pin)
|
||||
print("set " + str(g.read(pin)))
|
||||
time.sleep(1)
|
||||
g.clear(pin)
|
||||
print("clear " + str(g.read(pin)))
|
||||
time.sleep(1)
|
||||
cma = CMAPhysicalMemory(1*1024*1024)
|
||||
print(str(cma.get_size() / 1024 / 1024) + "MB of memory allocated at " \
|
||||
+ hex(cma.get_phys_address()))
|
||||
a = cma.read_int(0)
|
||||
print("was " + hex(a))
|
||||
cma.write_int(0, 0x12345678)
|
||||
a = cma.read_int(0)
|
||||
assert a == 0x12345678, "Memory isn't written or read correctly"
|
||||
print("now " + hex(a))
|
||||
del cma
|
||||
dg = DMAGPIO()
|
||||
dg.add_pulse(1 << pin, 4000)
|
||||
dg.add_delay(12000)
|
||||
dg.run(True)
|
||||
print("dmagpio is started")
|
||||
try:
|
||||
print("press enter to stop...")
|
||||
sys.stdin.readline()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
dg.stop()
|
||||
g.clear(pin)
|
||||
print("dma stopped")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,198 @@
|
||||
import os
|
||||
import mmap
|
||||
import struct
|
||||
import re
|
||||
import fcntl
|
||||
import array
|
||||
import atexit
|
||||
import ctypes
|
||||
|
||||
# Raspberry Pi registers
|
||||
# https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
|
||||
RPI1_PERI_BASE = 0x20000000
|
||||
RPI2_3_PERI_BASE = 0x3F000000
|
||||
# detect board version
|
||||
with open("/proc/cpuinfo", "r") as f:
|
||||
d = f.read()
|
||||
r = re.search("^Revision\s+:\s+(.+)$", d, flags=re.MULTILINE)
|
||||
h = re.search("^Hardware\s+:\s+(.+)$", d, flags=re.MULTILINE)
|
||||
RPI_1_REVISIONS = ['0002', '0003', '0004', '0005', '0006', '0007', '0008',
|
||||
'0009', '000d', '000e', '000f', '0010', '0011', '0012',
|
||||
'0013', '0014', '0015', '900021', '900032']
|
||||
if h is None:
|
||||
raise ImportError("This is not raspberry pi board.")
|
||||
elif r.group(1) in RPI_1_REVISIONS:
|
||||
PERI_BASE = RPI1_PERI_BASE
|
||||
elif "BCM2" in h.group(1):
|
||||
PERI_BASE = RPI2_3_PERI_BASE
|
||||
else:
|
||||
raise ImportError("Unknown board.")
|
||||
PAGE_SIZE = 4096
|
||||
GPIO_REGISTER_BASE = 0x200000
|
||||
GPIO_INPUT_OFFSET = 0x34
|
||||
GPIO_SET_OFFSET = 0x1C
|
||||
GPIO_CLEAR_OFFSET = 0x28
|
||||
GPIO_FSEL_OFFSET = 0x0
|
||||
GPIO_PULLUPDN_OFFSET = 0x94
|
||||
GPIO_PULLUPDNCLK_OFFSET = 0x98
|
||||
PHYSICAL_GPIO_BUS = 0x7E000000 + GPIO_REGISTER_BASE
|
||||
|
||||
# registers and values for DMA
|
||||
DMA_BASE = 0x007000
|
||||
DMA_TI_NO_WIDE_BURSTS = 1 << 26
|
||||
DMA_TI_SRC_INC = 1 << 8
|
||||
DMA_TI_DEST_INC = 1 << 4
|
||||
DMA_SRC_IGNORE = 1 << 11
|
||||
DMA_DEST_IGNORE = 1 << 7
|
||||
DMA_TI_TDMODE = 1 << 1
|
||||
DMA_TI_WAIT_RESP = 1 << 3
|
||||
DMA_TI_SRC_DREQ = 1 << 10
|
||||
DMA_TI_DEST_DREQ = 1 << 6
|
||||
DMA_CS_RESET = 1 << 31
|
||||
DMA_CS_ABORT = 1 << 30
|
||||
DMA_CS_DISDEBUG = 1 << 28
|
||||
DMA_CS_END = 1 << 1
|
||||
DMA_CS_ACTIVE = 1 << 0
|
||||
DMA_TI_PER_MAP_PWM = 5
|
||||
DMA_TI_PER_MAP_PCM = 2
|
||||
|
||||
def DMA_TI_PER_MAP(x):
|
||||
return x << 16
|
||||
|
||||
def DMA_TI_TXFR_LEN_YLENGTH(y):
|
||||
return (y & 0x3fff) << 16
|
||||
|
||||
def DMA_TI_TXFR_LEN_XLENGTH(x):
|
||||
return x & 0xffff
|
||||
|
||||
def DMA_TI_STRIDE_D_STRIDE(x):
|
||||
return (x & 0xffff) << 16
|
||||
|
||||
def DMA_TI_STRIDE_S_STRIDE(x):
|
||||
return x & 0xffff
|
||||
|
||||
def DMA_CS_PRIORITY(x):
|
||||
return (x & 0xf) << 16
|
||||
|
||||
def DMA_CS_PANIC_PRIORITY(x):
|
||||
return (x & 0xf) << 20
|
||||
|
||||
# hardware PWM controller registers
|
||||
PWM_BASE = 0x0020C000
|
||||
PWM_CTL= 0x00
|
||||
PWM_DMAC = 0x08
|
||||
PWM_RNG1 = 0x10
|
||||
PWM_FIFO = 0x18
|
||||
PWM_CTL_MODE1 = 1 << 1
|
||||
PWM_CTL_PWEN1 = 1 << 0
|
||||
PWM_CTL_CLRF = 1 << 6
|
||||
PWM_CTL_USEF1 = 1 << 5
|
||||
PWM_DMAC_ENAB = 1 << 31
|
||||
PHYSICAL_PWM_BUS = 0x7E000000 + PWM_BASE
|
||||
|
||||
def PWM_DMAC_PANIC(x):
|
||||
return x << 8
|
||||
|
||||
def PWM_DMAC_DREQ(x):
|
||||
return x
|
||||
|
||||
# clock manager module
|
||||
CM_BASE = 0x00101000
|
||||
CM_CNTL = 40
|
||||
CM_DIV = 41
|
||||
CM_PASSWORD = 0x5A << 24
|
||||
CM_ENABLE = 1 << 4
|
||||
CM_SRC_OSC = 1 # 19.2 MHz
|
||||
CM_SRC_PLLC = 5 # 1000 MHz
|
||||
CM_SRC_PLLD = 6 # 500 MHz
|
||||
CM_SRC_HDMI = 7 # 216 MHz
|
||||
|
||||
def CM_DIV_VALUE(x):
|
||||
return x << 12
|
||||
|
||||
|
||||
class PhysicalMemory(object):
|
||||
def __init__(self, phys_address, size=PAGE_SIZE):
|
||||
""" Create object which maps physical memory to Python's mmap object.
|
||||
:param phys_address: based address of physical memory
|
||||
"""
|
||||
self._size = size
|
||||
phys_address -= phys_address % PAGE_SIZE
|
||||
fd = self._open_dev("/dev/mem")
|
||||
self._rmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED,
|
||||
prot=mmap.PROT_READ | mmap.PROT_WRITE,
|
||||
offset=phys_address)
|
||||
atexit.register(self.cleanup)
|
||||
|
||||
def cleanup(self):
|
||||
self._rmap.close()
|
||||
|
||||
@staticmethod
|
||||
def _open_dev(name):
|
||||
fd = os.open(name, os.O_SYNC | os.O_RDWR | os.O_LARGEFILE)
|
||||
if fd < 0:
|
||||
raise IOError("Failed to open " + name)
|
||||
return fd
|
||||
|
||||
@staticmethod
|
||||
def _close_dev(fd):
|
||||
os.close(fd)
|
||||
|
||||
def write_int(self, address, int_value):
|
||||
self._rmap[address:address + 4] = struct.pack("I", int_value)
|
||||
|
||||
def write(self, address, data):
|
||||
self._rmap.seek(address)
|
||||
self._rmap.write(struct.pack(str(len(data)) + "I", *data))
|
||||
|
||||
def read_int(self, address):
|
||||
return struct.unpack("I", self._rmap[address:address + 4])[0]
|
||||
|
||||
def get_size(self):
|
||||
return self._size
|
||||
|
||||
|
||||
class CMAPhysicalMemory(PhysicalMemory):
|
||||
IOCTL_MBOX_PROPERTY = ctypes.c_long(0xc0046400).value
|
||||
def __init__(self, size):
|
||||
""" This class allocates continuous memory with specified size, lock it
|
||||
and provide access to it with Python's mmap. It uses RPi video
|
||||
buffers to allocate it (/dev/vcio).
|
||||
:param size: number of bytes to allocate
|
||||
"""
|
||||
size = (size + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
||||
self._vcio_fd = self._open_dev("/dev/vcio")
|
||||
self._handle = self._send_data(0x3000c, [size, PAGE_SIZE, 0xC]) # allocate memory
|
||||
if self._handle == 0:
|
||||
raise OSError("No memory to allocate with /dev/vcio")
|
||||
self._busmem = self._send_data(0x3000d, [self._handle]) # lock memory
|
||||
if self._busmem == 0:
|
||||
# memory should be freed in __del__
|
||||
raise OSError("Failed to lock memory with /dev/vcio")
|
||||
# print("allocate {} at {} (bus {})".format(size,
|
||||
# hex(self.get_phys_address()), hex(self.get_bus_address())))
|
||||
super(CMAPhysicalMemory, self).__init__(self.get_phys_address(), size)
|
||||
atexit.register(self.free)
|
||||
|
||||
def free(self):
|
||||
self._send_data(0x3000e, [self._handle]) # unlock memory
|
||||
self._send_data(0x3000f, [self._handle]) # free memory
|
||||
self._close_dev(self._vcio_fd)
|
||||
|
||||
def _send_data(self, request, args):
|
||||
data = array.array('I')
|
||||
data.append(24 + 4 * len(args)) # total size
|
||||
data.append(0) # process request
|
||||
data.append(request) # request id
|
||||
data.append(4 * len(args)) # size of the buffer
|
||||
data.append(4 * len(args)) # size of the data
|
||||
data.extend(args) # arguments
|
||||
data.append(0) # end mark
|
||||
fcntl.ioctl(self._vcio_fd, self.IOCTL_MBOX_PROPERTY, data, True)
|
||||
return data[5]
|
||||
|
||||
def get_bus_address(self):
|
||||
return self._busmem
|
||||
|
||||
def get_phys_address(self):
|
||||
return self._busmem & ~0xc0000000
|
||||
Reference in New Issue
Block a user