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 try: 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.") except IOError: raise ImportError("/proc/cpuinfo not found. Not Linux device?") 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_CS = 0x00 DMA_CONBLK_AD = 0x04 DMA_NEXTCONBK = 0x1C 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 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_XLENGTH = (lambda x: x & 0xffff) DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16) DMA_TI_STRIDE_S_STRIDE = (lambda x: x & 0xffff) DMA_CS_PRIORITY = (lambda x: (x & 0xf) << 16) DMA_CS_PANIC_PRIORITY = (lambda x: (x & 0xf) << 20) # hardware PWM controller registers PWM_BASE = 0x0020C000 PHYSICAL_PWM_BUS = 0x7E000000 + PWM_BASE PWM_CTL = 0x00 PWM_DMAC = 0x08 PWM_RNG1 = 0x10 PWM_RNG2 = 0x20 PWM_FIFO = 0x18 PWM_CTL_MODE1 = 1 << 1 PWM_CTL_MODE2 = 1 << 9 PWM_CTL_PWEN1 = 1 << 0 PWM_CTL_PWEN2 = 1 << 8 PWM_CTL_CLRF = 1 << 6 PWM_CTL_USEF1 = 1 << 5 PWM_CTL_USEF2 = 1 << 13 PWM_DMAC_ENAB = 1 << 31 PWM_DMAC_PANIC = (lambda x: x << 8) PWM_DMAC_DREQ = (lambda x: x) # clock manager module CM_BASE = 0x00101000 CM_PCM_CNTL = 0x98 CM_PCM_DIV = 0x9C CM_PWM_CNTL = 0xA0 CM_PWM_DIV = 0xA4 CM_PASSWORD = 0x5A << 24 CM_CNTL_ENABLE = 1 << 4 CM_CNTL_BUSY = 1 << 7 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 CM_DIV_VALUE = (lambda x: x << 12) class PhysicalMemory(object): # noinspection PyArgumentList,PyArgumentList 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._memmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=phys_address) self._close_dev(fd) atexit.register(self.cleanup) def cleanup(self): self._memmap.close() @staticmethod def _open_dev(name): fd = os.open(name, os.O_SYNC | os.O_RDWR) 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): ctypes.c_uint32.from_buffer(self._memmap, address).value = int_value def write(self, address, fmt, data): struct.pack_into(fmt, self._memmap, address, *data) def read_int(self, address): return ctypes.c_uint32.from_buffer(self._memmap, address).value 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") # allocate memory self._handle = self._send_data(0x3000c, [size, PAGE_SIZE, 0xC]) if self._handle == 0: raise OSError("No memory to allocate with /dev/vcio") # lock memory self._bus_memory = self._send_data(0x3000d, [self._handle]) if self._bus_memory == 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): """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) 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._bus_memory def get_phys_address(self): return self._bus_memory & ~0xc0000000 class DMAProto(object): def __init__(self, memory_size, dma_channel): """ This class provides basic access to DMA and creates buffer for control blocks. """ self._DMA_CHANNEL_ADDRESS = 0x100 * dma_channel # allocate buffer for control blocks self._phys_memory = 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. """ 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(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) cs |= DMA_CS_ACTIVE self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) def _stop_dma(self): """ Stop DMA """ cs = self._dma.read_int(self._DMA_CHANNEL_ADDRESS + DMA_CS) cs |= DMA_CS_ABORT self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) cs &= ~DMA_CS_ACTIVE self._dma.write_int(self._DMA_CHANNEL_ADDRESS + DMA_CS, cs) cs |= DMA_CS_RESET 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 """ 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()