From f90f1db55859cf3fa84eb4d04ab7f7bd7471066f Mon Sep 17 00:00:00 2001 From: Adam Thayer Date: Tue, 31 Jul 2018 14:46:12 -0700 Subject: [PATCH 1/6] Hack and Burn Refactoring of RaspberryPWM This is the first step towards getting better PWM smoothness, which is to separate out the PHY layer from the behavioral layer that sits right on top of it. By doing this, we can now share a little bit of PHY state between each RaspberryPWM instance. This currently disables the DMA-portion, and breaks the pattern PWM functionality. This is because each pin can and should have its own DMA channel, and needs its own DMA address pointer to work correctly. This is normally done in initPWM(), and I need to figure out the best solution for handling this piece going forward. It will probably involve recording all the DMA addresses when we init the phy, and letting each PWM get the channel they need from the PHY. --- Sources/PWM.swift | 217 ++++++++++++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 74 deletions(-) diff --git a/Sources/PWM.swift b/Sources/PWM.swift index c3e1dab..aa8f2ad 100644 --- a/Sources/PWM.swift +++ b/Sources/PWM.swift @@ -49,19 +49,23 @@ extension SwiftyGPIO { } } -// MARK: - SPI Presets +// MARK: - PWM Presets extension SwiftyGPIO { // RaspberryPis ARMv6 (all 1, Zero, Zero W) PWMs, only accessible ones, divided in channels (can use only one for each channel) static let PWMRPI1: [Int:[GPIOName:PWMOutput]] = [ - 0: [.P12: RaspberryPWM(gpioId: 12, alt: 0, channel:0, baseAddr: 0x20000000), .P18: RaspberryPWM(gpioId: 18, alt: 5, channel:0, baseAddr: 0x20000000)], - 1: [.P13: RaspberryPWM(gpioId: 13, alt: 0, channel:1, baseAddr: 0x20000000), .P19: RaspberryPWM(gpioId: 19, alt: 5, channel:1, baseAddr: 0x20000000)] + 0: [.P12: RaspberryPWM(gpioId: 12, alt: 0, channel:0, phy: RaspberryPHY.phyFor(baseAddr: 0x20000000)), + .P18: RaspberryPWM(gpioId: 18, alt: 5, channel:0, phy: RaspberryPHY.phyFor(baseAddr: 0x20000000))], + 1: [.P13: RaspberryPWM(gpioId: 13, alt: 0, channel:1, phy: RaspberryPHY.phyFor(baseAddr: 0x20000000)), + .P19: RaspberryPWM(gpioId: 19, alt: 5, channel:1, phy: RaspberryPHY.phyFor(baseAddr: 0x20000000))] ] // RaspberryPis ARMv7 (2-3) PWMs, only accessible ones, divided in channels (can use only one for each channel) static let PWMRPI23: [Int:[GPIOName:PWMOutput]] = [ - 0: [.P12: RaspberryPWM(gpioId: 12, alt: 0, channel:0, baseAddr: 0x3F000000), .P18: RaspberryPWM(gpioId: 18, alt: 5, channel:0, baseAddr: 0x3F000000)], - 1: [.P13: RaspberryPWM(gpioId: 13, alt: 0, channel:1, baseAddr: 0x3F000000), .P19: RaspberryPWM(gpioId: 19, alt: 5, channel:1, baseAddr: 0x3F000000)] + 0: [.P12: RaspberryPWM(gpioId: 12, alt: 0, channel:0, phy: RaspberryPHY.phyFor(baseAddr: 0x3F000000)), + .P18: RaspberryPWM(gpioId: 18, alt: 5, channel:0, phy: RaspberryPHY.phyFor(baseAddr: 0x3F000000))], + 1: [.P13: RaspberryPWM(gpioId: 13, alt: 0, channel:1, phy: RaspberryPHY.phyFor(baseAddr: 0x3F000000)), + .P19: RaspberryPWM(gpioId: 19, alt: 5, channel:1, phy: RaspberryPHY.phyFor(baseAddr: 0x3F000000))] ] } @@ -78,11 +82,18 @@ public protocol PWMOutput { func cleanupPattern() } -public class RaspberryPWM: PWMOutput { - let gpioId: UInt - let alt: UInt - let channel: Int - let pwmdma: Int +final public class RaspberryPHY { + private static var existingPhys: [Int: RaspberryPHY] = [:] + + static func phyFor(baseAddr: Int) -> RaspberryPHY { + if let phy = existingPhys[baseAddr] { + return phy + } + + let phy = RaspberryPHY(baseAddr: baseAddr) + existingPhys[baseAddr] = phy + return phy + } let BCM2708_PERI_BASE: Int let GPIO_BASE: Int // GPIO Register @@ -97,20 +108,10 @@ public class RaspberryPWM: PWMOutput { var dmaBasePointer: UnsafeMutablePointer! var dmaCallbackPointer: UnsafeMutablePointer! = nil var pwmRawPointer: UnsafeMutablePointer! = nil - var mailbox: MailBox! = nil - var zeroPattern: Int = 0 - var onePattern: Int = 0 - var symbolBits: Int = 0 - var patternFrequency: Int = 0 - var patternDelay: Int = 0 - var dataLength: Int = 0 + public private(set) var isReadyForPwm: Bool = false - public init(gpioId: UInt, alt: UInt, channel: Int, baseAddr: Int, dmanum: Int = 5) { - self.gpioId = gpioId - self.alt = alt - self.channel = channel - self.pwmdma = dmanum + private init(baseAddr: Int) { BCM2708_PERI_BASE = baseAddr GPIO_BASE = BCM2708_PERI_BASE + 0x200000 // GPIO Register PWM_BASE = BCM2708_PERI_BASE + 0x20C000 // PWM Register @@ -118,8 +119,7 @@ public class RaspberryPWM: PWMOutput { PWM_PHY_BASE = BCM2708_PHY_BASE + 0x20C000 // PWM controller physical address } - /// Init PWM on this pin, set alternative function - public func initPWM() { + func initPhy() { var mem_fd: Int32 = 0 //The only mem device that support PWM is /dev/mem @@ -133,7 +133,7 @@ public class RaspberryPWM: PWMOutput { pwmBasePointer = memmap(from: mem_fd, at: PWM_BASE) clockBasePointer = memmap(from: mem_fd, at: CLOCK_BASE) - let DMAOffsets: [Int] = [0x00007000, 0x00007100, 0x00007200, 0x00007300, + /*let DMAOffsets: [Int] = [0x00007000, 0x00007100, 0x00007200, 0x00007300, 0x00007400, 0x00007500, 0x00007600, 0x00007700, 0x00007800, 0x00007900, 0x00007a00, 0x00007b00, 0x00007c00, 0x00007d00, 0x00007e00, 0x00e05000] @@ -148,9 +148,81 @@ public class RaspberryPWM: PWMOutput { dma_addr -= pageOffset let dma_map = UnsafeMutableRawPointer(memmap(from: mem_fd, at: dma_addr)) - dmaBasePointer = (dma_map + pageOffset).assumingMemoryBound(to: UInt.self) + dmaBasePointer = (dma_map + pageOffset).assumingMemoryBound(to: UInt.self)*/ close(mem_fd) + } + + func setPwmClock() { + guard !isReadyForPwm else { return } + + killClock() + + let idiv = UInt(2) // 250Mhz base frequency + // Configure the clock and divisor that will be used to generate the signal + clockBasePointer.advanced(by: 41).pointee = CLKM_PASSWD | (idiv << CLKM_DIV_DIVI) //CM CTL DIV register: Set DIVI value + clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_ENAB | CLKM_CTL_SRC_PLLD //CM CTL register: Enable clock, MASH 0, source PLLD + pwmBasePointer.pointee = 0 //PWM CTL register: Everything at 0, enable flag included, disables previous PWM + usleep(10) + + isReadyForPwm = true + } + + func killClock() { + clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //CM CTL register: Set KILL flag + isReadyForPwm = false + usleep(10) + } + + /// Maps a block of memory and returns the pointer + internal func memmap(from mem_fd: Int32, at offset: Int) -> UnsafeMutablePointer { + let m = mmap( + nil, //Any adddress in our space will do + PAGE_SIZE, //Map length + PROT_READ|PROT_WRITE, // Enable reading & writting to mapped memory + MAP_SHARED, //Shared with other processes + mem_fd, //File to map + off_t(offset) //Offset to GPIO peripheral + )! + + if (Int(bitPattern: m) == -1) { //MAP_FAILED not available, but its value is (void*)-1 + perror("mmap error") + abort() + } + let pointer = m.assumingMemoryBound(to: UInt.self) + + return pointer + } +} + +public class RaspberryPWM: PWMOutput { + let gpioId: UInt + let alt: UInt + let channel: Int + let pwmdma: Int + + let phy: RaspberryPHY + + var mailbox: MailBox! = nil + + var zeroPattern: Int = 0 + var onePattern: Int = 0 + var symbolBits: Int = 0 + var patternFrequency: Int = 0 + var patternDelay: Int = 0 + var dataLength: Int = 0 + + public init(gpioId: UInt, alt: UInt, channel: Int, phy: RaspberryPHY, dmanum: Int = 5) { + self.phy = phy + self.gpioId = gpioId + self.alt = alt + self.channel = channel + self.pwmdma = dmanum + } + + /// Init PWM on this pin, set alternative function + public func initPWM() { + phy.initPhy() // set PWM alternate function for this GPIO setAlt() @@ -160,33 +232,30 @@ public class RaspberryPWM: PWMOutput { /// The signal starts, asynchronously(manged by a device external to the CPU), once this method is called and /// needs to be stopped manually calling `stopPWM()`. public func startPWM(period ns: Int, duty percent: Float) { - // Kill the clock - clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //CM CTL register: Set KILL flag - usleep(10) - - // If the required frequency is too high, this value reduces the number of samples (scale does the opposite) - let highFreqSampleReduction: UInt = (ns < 750) ? 10 : 1 + if !phy.isReadyForPwm { + phy.setPwmClock() + } - let freq: UInt = (1_000_000_000/UInt(ns)) * 100 / highFreqSampleReduction - let (idiv, scale) = calculateDIVI(base: .PLLD, desired: freq) //Using the faster (with known freq) available clock to reduce jitter + // Need to provide for a minimum period here. + let range = UInt(max(ns, 750)) + let data = UInt(percent * Float(ns) / 100.0) - // Configure the clock and divisor that will be used to generate the signal - clockBasePointer.advanced(by: 41).pointee = CLKM_PASSWD | (idiv << CLKM_DIV_DIVI) //CM CTL DIV register: Set DIVI value - clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_ENAB | CLKM_CTL_SRC_PLLD //CM CTL register: Enable clock, MASH 0, source PLLD - pwmBasePointer.pointee = 0 //PWM CTL register: Everything at 0, enable flag included, disables previous PWM - usleep(10) // Configure the parameters for the M/S algorithm, S the number of total slots in RNG1 and M the number of slots with high value in DAT1 let RNG = (channel == 0) ? 4 : 8 let DAT = (channel == 0) ? 5 : 9 - pwmBasePointer.advanced(by: RNG).pointee = 100 * scale / highFreqSampleReduction //RNG1 register - pwmBasePointer.advanced(by: DAT).pointee = UInt((percent / Float(highFreqSampleReduction)) * Float(scale)) //DAT1 register + phy.pwmBasePointer.advanced(by: RNG).pointee = range //RNG1 register + phy.pwmBasePointer.advanced(by: DAT).pointee = data //DAT1 register let PWMCTL_MSEN = (channel == 0) ? PWMCTL_MSEN1 : PWMCTL_MSEN2 let PWMCTL_PWEN = (channel == 0) ? PWMCTL_PWEN1 : PWMCTL_PWEN2 - pwmBasePointer.pointee = PWMCTL_MSEN | PWMCTL_PWEN //PWM CTL register, channel enabled, M/S mode + let oldCtl = phy.pwmBasePointer.pointee + phy.pwmBasePointer.pointee = oldCtl | PWMCTL_MSEN | PWMCTL_PWEN //PWM CTL register, channel enabled, M/S mode } public func stopPWM() { - pwmBasePointer.pointee = 0 //PWM CTL register, everything at 0, enable flag included + let PWMCTL_MSEN = (channel == 0) ? PWMCTL_MSEN1 : PWMCTL_MSEN2 + let PWMCTL_PWEN = (channel == 0) ? PWMCTL_PWEN1 : PWMCTL_PWEN2 + let oldCtl = phy.pwmBasePointer.pointee + phy.pwmBasePointer.pointee = oldCtl & ~PWMCTL_MSEN & ~PWMCTL_PWEN //PWM CTL register, everything at 0, enable flag included } /// Maps a block of memory and returns the pointer @@ -212,7 +281,7 @@ public class RaspberryPWM: PWMOutput { /// Set the alternative function for this GPIO internal func setAlt() { let altid = (self.alt<=3) ? self.alt+4 : self.alt==4 ? 3 : 2 - let ptr = gpioBasePointer.advanced(by: Int(gpioId/10)) // GPFSELn 0..5 + let ptr = phy.gpioBasePointer.advanced(by: Int(gpioId/10)) // GPFSELn 0..5 ptr.pointee &= ~(7<<((gpioId%10)*3)) ptr.pointee |= (altid<<((gpioId%10)*3)) } @@ -275,22 +344,22 @@ extension RaspberryPWM { /// Start the DMA feeding the PWM FIFO. This will stream the entire DMA buffer out of both PWM channels. internal func dma_start(dmaCallback address: UInt) { - dmaBasePointer.pointee = DMACS_RESET + phy.dmaBasePointer.pointee = DMACS_RESET usleep(10) - dmaBasePointer.pointee = DMACS_INT | DMACS_END - dmaBasePointer.advanced(by: 1).pointee = address //CONBLK_AD - dmaBasePointer.advanced(by: 8).pointee = 7 //DEBUG: clear debug error flags - dmaBasePointer.pointee = DMACS_WAIT_OUTSTANDING_WRITES | (15 << DMACS_PANIC_PRIORITY) | (15 << DMACS_PRIORITY) | DMACS_ACTIVE + phy.dmaBasePointer.pointee = DMACS_INT | DMACS_END + phy.dmaBasePointer.advanced(by: 1).pointee = address //CONBLK_AD + phy.dmaBasePointer.advanced(by: 8).pointee = 7 //DEBUG: clear debug error flags + phy.dmaBasePointer.pointee = DMACS_WAIT_OUTSTANDING_WRITES | (15 << DMACS_PANIC_PRIORITY) | (15 << DMACS_PRIORITY) | DMACS_ACTIVE } /// Wait for any executing DMA operation to complete before returning. internal func dma_wait() { - while (dmaBasePointer.pointee & DMACS_ACTIVE > 0) && !(dmaBasePointer.pointee & DMACS_ERROR > 0) { + while (phy.dmaBasePointer.pointee & DMACS_ACTIVE > 0) && !(phy.dmaBasePointer.pointee & DMACS_ERROR > 0) { usleep(10) } - if (dmaBasePointer.pointee & DMACS_ERROR)>0 { - fatalError("DMA Error: \(dmaBasePointer.advanced(by: 8).pointee)") + if (phy.dmaBasePointer.pointee & DMACS_ERROR)>0 { + fatalError("DMA Error: \(phy.dmaBasePointer.advanced(by: 8).pointee)") } } @@ -303,10 +372,10 @@ extension RaspberryPWM { public func cleanupPattern() { dma_wait() // Stop the PWM - pwmBasePointer.pointee = 0 + phy.pwmBasePointer.pointee = 0 usleep(10) // Stop the clock killing the clock - clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //Set KILL flag + phy.clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //Set KILL flag usleep(10) mailbox.cleanup() @@ -335,62 +404,62 @@ extension RaspberryPWM { // Round up to page size multiple let mboxsize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1) - mailbox = MailBox(handle: -1, size: mboxsize, isRaspi2: BCM2708_PERI_BASE != 0x20000000) + mailbox = MailBox(handle: -1, size: mboxsize, isRaspi2: phy.BCM2708_PERI_BASE != 0x20000000) guard let mailbox = mailbox else {fatalError("Could allocate mailbox.")} - dmaCallbackPointer = mailbox.baseVirtualAddress.assumingMemoryBound(to: DMACallback.self) - pwmRawPointer = (mailbox.baseVirtualAddress + MemoryLayout.stride).assumingMemoryBound(to: UInt.self) + phy.dmaCallbackPointer = mailbox.baseVirtualAddress.assumingMemoryBound(to: DMACallback.self) + phy.pwmRawPointer = (mailbox.baseVirtualAddress + MemoryLayout.stride).assumingMemoryBound(to: UInt.self) // Fill PWM buffer with zeros let rows = dataSize / MemoryLayout.stride for pos in 0..=4.1) - UnsafeMutableRawPointer(pwmRawPointer).copyMemory(from: ptr.baseAddress!, byteCount: stream.count * MemoryLayout.stride) + UnsafeMutableRawPointer(phy.pwmRawPointer).copyMemory(from: ptr.baseAddress!, byteCount: stream.count * MemoryLayout.stride) #else - UnsafeMutableRawPointer(pwmRawPointer).copyBytes(from: ptr.baseAddress!, count: stream.count * MemoryLayout.stride) + UnsafeMutableRawPointer(phy.pwmRawPointer).copyBytes(from: ptr.baseAddress!, count: stream.count * MemoryLayout.stride) #endif } From d9719d9cf566fd0e8a5a4104157fd3b8b75cbc62 Mon Sep 17 00:00:00 2001 From: Adam Thayer Date: Tue, 31 Jul 2018 15:10:54 -0700 Subject: [PATCH 2/6] Restore DMA Behavior This change should restore the DMA behavior after the refactoring to extract the PHY out. When initializing the PHY for the first time. Even though we are getting information for more than one DMA channel on initialization now, the offset cost of not having to memmap /dev/mem for every channel helps here, if you need more than one DMA channel. --- Sources/PWM.swift | 66 ++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/Sources/PWM.swift b/Sources/PWM.swift index aa8f2ad..b7c09b6 100644 --- a/Sources/PWM.swift +++ b/Sources/PWM.swift @@ -105,10 +105,9 @@ final public class RaspberryPHY { var gpioBasePointer: UnsafeMutablePointer! var pwmBasePointer: UnsafeMutablePointer! var clockBasePointer: UnsafeMutablePointer! - var dmaBasePointer: UnsafeMutablePointer! - var dmaCallbackPointer: UnsafeMutablePointer! = nil - var pwmRawPointer: UnsafeMutablePointer! = nil + var dmaBasePointers: [UnsafeMutablePointer]! + public private(set) var isInitialized: Bool = false public private(set) var isReadyForPwm: Bool = false private init(baseAddr: Int) { @@ -120,6 +119,8 @@ final public class RaspberryPHY { } func initPhy() { + guard !isInitialized else { return } + var mem_fd: Int32 = 0 //The only mem device that support PWM is /dev/mem @@ -133,22 +134,19 @@ final public class RaspberryPHY { pwmBasePointer = memmap(from: mem_fd, at: PWM_BASE) clockBasePointer = memmap(from: mem_fd, at: CLOCK_BASE) - /*let DMAOffsets: [Int] = [0x00007000, 0x00007100, 0x00007200, 0x00007300, + let DMAOffsets: [Int] = [0x00007000, 0x00007100, 0x00007200, 0x00007300, 0x00007400, 0x00007500, 0x00007600, 0x00007700, 0x00007800, 0x00007900, 0x00007a00, 0x00007b00, 0x00007c00, 0x00007d00, 0x00007e00, 0x00e05000] - func dmanumToPhysicalAddress(_ dmanum: Int) -> Int { - guard dmanum < DMAOffsets.count else { return 0 } - return BCM2708_PERI_BASE + DMAOffsets[dmanum] - } - - var dma_addr = dmanumToPhysicalAddress(pwmdma) // Address of a specific DMA Channel registers set - let pageOffset = dma_addr % PAGE_SIZE - dma_addr -= pageOffset + for dma_addr in DMAOffsets.map({ BCM2708_PERI_BASE + $0 }) { + let pageOffset = dma_addr % PAGE_SIZE + let adjusted_dma_addr = dma_addr - pageOffset + let dma_map = UnsafeMutableRawPointer(memmap(from: mem_fd, at: adjusted_dma_addr)) - let dma_map = UnsafeMutableRawPointer(memmap(from: mem_fd, at: dma_addr)) - dmaBasePointer = (dma_map + pageOffset).assumingMemoryBound(to: UInt.self)*/ + let dmaBasePointer = (dma_map + pageOffset).assumingMemoryBound(to: UInt.self) + dmaBasePointers.append(dmaBasePointer) + } close(mem_fd) } @@ -203,6 +201,9 @@ public class RaspberryPWM: PWMOutput { let phy: RaspberryPHY + var dmaBasePointer: UnsafeMutablePointer! + var dmaCallbackPointer: UnsafeMutablePointer! = nil + var pwmRawPointer: UnsafeMutablePointer! = nil var mailbox: MailBox! = nil var zeroPattern: Int = 0 @@ -223,6 +224,7 @@ public class RaspberryPWM: PWMOutput { /// Init PWM on this pin, set alternative function public func initPWM() { phy.initPhy() + dmaBasePointer = phy.dmaBasePointers[pwmdma] // set PWM alternate function for this GPIO setAlt() @@ -255,7 +257,7 @@ public class RaspberryPWM: PWMOutput { let PWMCTL_MSEN = (channel == 0) ? PWMCTL_MSEN1 : PWMCTL_MSEN2 let PWMCTL_PWEN = (channel == 0) ? PWMCTL_PWEN1 : PWMCTL_PWEN2 let oldCtl = phy.pwmBasePointer.pointee - phy.pwmBasePointer.pointee = oldCtl & ~PWMCTL_MSEN & ~PWMCTL_PWEN //PWM CTL register, everything at 0, enable flag included + phy.pwmBasePointer.pointee = oldCtl & ~PWMCTL_MSEN & ~PWMCTL_PWEN //PWM CTL register, clear just the channel. } /// Maps a block of memory and returns the pointer @@ -344,22 +346,22 @@ extension RaspberryPWM { /// Start the DMA feeding the PWM FIFO. This will stream the entire DMA buffer out of both PWM channels. internal func dma_start(dmaCallback address: UInt) { - phy.dmaBasePointer.pointee = DMACS_RESET + dmaBasePointer.pointee = DMACS_RESET usleep(10) - phy.dmaBasePointer.pointee = DMACS_INT | DMACS_END - phy.dmaBasePointer.advanced(by: 1).pointee = address //CONBLK_AD - phy.dmaBasePointer.advanced(by: 8).pointee = 7 //DEBUG: clear debug error flags - phy.dmaBasePointer.pointee = DMACS_WAIT_OUTSTANDING_WRITES | (15 << DMACS_PANIC_PRIORITY) | (15 << DMACS_PRIORITY) | DMACS_ACTIVE + dmaBasePointer.pointee = DMACS_INT | DMACS_END + dmaBasePointer.advanced(by: 1).pointee = address //CONBLK_AD + dmaBasePointer.advanced(by: 8).pointee = 7 //DEBUG: clear debug error flags + dmaBasePointer.pointee = DMACS_WAIT_OUTSTANDING_WRITES | (15 << DMACS_PANIC_PRIORITY) | (15 << DMACS_PRIORITY) | DMACS_ACTIVE } /// Wait for any executing DMA operation to complete before returning. internal func dma_wait() { - while (phy.dmaBasePointer.pointee & DMACS_ACTIVE > 0) && !(phy.dmaBasePointer.pointee & DMACS_ERROR > 0) { + while (dmaBasePointer.pointee & DMACS_ACTIVE > 0) && !(dmaBasePointer.pointee & DMACS_ERROR > 0) { usleep(10) } - if (phy.dmaBasePointer.pointee & DMACS_ERROR)>0 { - fatalError("DMA Error: \(phy.dmaBasePointer.advanced(by: 8).pointee)") + if (dmaBasePointer.pointee & DMACS_ERROR)>0 { + fatalError("DMA Error: \(dmaBasePointer.advanced(by: 8).pointee)") } } @@ -408,13 +410,13 @@ extension RaspberryPWM { guard let mailbox = mailbox else {fatalError("Could allocate mailbox.")} - phy.dmaCallbackPointer = mailbox.baseVirtualAddress.assumingMemoryBound(to: DMACallback.self) - phy.pwmRawPointer = (mailbox.baseVirtualAddress + MemoryLayout.stride).assumingMemoryBound(to: UInt.self) + dmaCallbackPointer = mailbox.baseVirtualAddress.assumingMemoryBound(to: DMACallback.self) + pwmRawPointer = (mailbox.baseVirtualAddress + MemoryLayout.stride).assumingMemoryBound(to: UInt.self) // Fill PWM buffer with zeros let rows = dataSize / MemoryLayout.stride for pos in 0..=4.1) - UnsafeMutableRawPointer(phy.pwmRawPointer).copyMemory(from: ptr.baseAddress!, byteCount: stream.count * MemoryLayout.stride) + UnsafeMutableRawPointer(pwmRawPointer).copyMemory(from: ptr.baseAddress!, byteCount: stream.count * MemoryLayout.stride) #else - UnsafeMutableRawPointer(phy.pwmRawPointer).copyBytes(from: ptr.baseAddress!, count: stream.count * MemoryLayout.stride) + UnsafeMutableRawPointer(pwmRawPointer).copyBytes(from: ptr.baseAddress!, count: stream.count * MemoryLayout.stride) #endif } From 2c5896f4ddf916a6efcf694d9ef60d765b3f2a55 Mon Sep 17 00:00:00 2001 From: Adam Thayer Date: Tue, 31 Jul 2018 18:04:46 -0700 Subject: [PATCH 3/6] Fix Typo with dmaBasePointers --- Sources/PWM.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PWM.swift b/Sources/PWM.swift index b7c09b6..e522204 100644 --- a/Sources/PWM.swift +++ b/Sources/PWM.swift @@ -105,7 +105,7 @@ final public class RaspberryPHY { var gpioBasePointer: UnsafeMutablePointer! var pwmBasePointer: UnsafeMutablePointer! var clockBasePointer: UnsafeMutablePointer! - var dmaBasePointers: [UnsafeMutablePointer]! + var dmaBasePointers: [UnsafeMutablePointer]! = [] public private(set) var isInitialized: Bool = false public private(set) var isReadyForPwm: Bool = false From f61a851466736ada32ac2dad26d85e366957774e Mon Sep 17 00:00:00 2001 From: Adam Thayer Date: Thu, 2 Aug 2018 14:52:30 -0700 Subject: [PATCH 4/6] Leverage Refactor to Clean up PWM Pattern Behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly the goal here is to re-use the killClock() function in the RaspberryPHY class so that when the PWM Pattern starts, it properly signals to the basic PWM side of things that it needs to reinitialize the clock before taking over the PWM channel. Because the PWM Patterns don’t operate independently yet, like the PWM mode now does, don’t bother coalescing on stopPWM(). --- Sources/PWM.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/PWM.swift b/Sources/PWM.swift index e522204..d63c5e6 100644 --- a/Sources/PWM.swift +++ b/Sources/PWM.swift @@ -168,8 +168,9 @@ final public class RaspberryPHY { func killClock() { clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //CM CTL register: Set KILL flag - isReadyForPwm = false usleep(10) + + isReadyForPwm = false } /// Maps a block of memory and returns the pointer @@ -376,9 +377,8 @@ extension RaspberryPWM { // Stop the PWM phy.pwmBasePointer.pointee = 0 usleep(10) - // Stop the clock killing the clock - phy.clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //Set KILL flag - usleep(10) + // Kill the Clock + phy.killClock() mailbox.cleanup() } @@ -423,8 +423,7 @@ extension RaspberryPWM { phy.pwmBasePointer.pointee = 0 usleep(10) // Stop the clock killing the clock - phy.clockBasePointer.advanced(by: 40).pointee = CLKM_PASSWD | CLKM_CTL_KILL //Set KILL flag - usleep(10) + phy.killClock() // Check the BUSY flag, doesn't always work //while (clockBasePointer.advanced(by: 40).pointee & (1 << 7)) != 0 {} From 2e3dbdcc06e3c1298e04f569f5bb944d1b2ff328 Mon Sep 17 00:00:00 2001 From: Adam Thayer Date: Wed, 5 Sep 2018 14:59:17 -0700 Subject: [PATCH 5/6] Only Cache a Single PHY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn’t make sense to a cache a PHY for each base address. This isn’t a real scenario. Instead, let’s just cache the last PHY we create, and invalidate the cache in the case of a mismatch. --- Sources/PWM.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/PWM.swift b/Sources/PWM.swift index d63c5e6..aa1c7f2 100644 --- a/Sources/PWM.swift +++ b/Sources/PWM.swift @@ -83,15 +83,18 @@ public protocol PWMOutput { } final public class RaspberryPHY { - private static var existingPhys: [Int: RaspberryPHY] = [:] + private static var existingPhy: RaspberryPHY? = nil static func phyFor(baseAddr: Int) -> RaspberryPHY { - if let phy = existingPhys[baseAddr] { + // If the physical base has changed, throw away the existing cache. + // This generally shouldn't happen, as the base address is fixed. + // It is reasonable to create a new instance if there is a mismatch, though. + if let phy = existingPhy, phy.BCM2708_PERI_BASE == baseAddr { return phy } let phy = RaspberryPHY(baseAddr: baseAddr) - existingPhys[baseAddr] = phy + existingPhy = phy return phy } From b05f1602c136cfcb6a2ea5609a755efe052a4c8e Mon Sep 17 00:00:00 2001 From: Adam Thayer Date: Wed, 5 Sep 2018 15:31:58 -0700 Subject: [PATCH 6/6] Address Issues Around Range/Data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A couple problems cropped up here, where the period wasn’t actually adjusted properly. It assumed the period was in ns, but it is in slots. Each slot is 4 ns. Account for that. Also, don’t lose precision when calculating very slow frequencies in the realm of 15Hz. --- Sources/PWM.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/PWM.swift b/Sources/PWM.swift index aa1c7f2..3678a3c 100644 --- a/Sources/PWM.swift +++ b/Sources/PWM.swift @@ -242,9 +242,14 @@ public class RaspberryPWM: PWMOutput { phy.setPwmClock() } - // Need to provide for a minimum period here. - let range = UInt(max(ns, 750)) - let data = UInt(percent * Float(ns) / 100.0) + // @ 250Mhz, one slot in the PWM channel is 4 ns. All valid periods are multiples of that. + // Pick a range that is at least as large as requested that we can represent, and round the + // desired duty cycle to the nearest whole slot. + // + // Double precision is used here to handle very low frequencies in the range of <15Hz. + // Otherwise we lose precision calculating data. + let range = UInt(max((Double(ns) / 4.0).rounded(.awayFromZero), 1)) + let data = min(UInt((Double(percent) * Double(range) / 100.0).rounded(.toNearestOrAwayFromZero)), range) // Configure the parameters for the M/S algorithm, S the number of total slots in RNG1 and M the number of slots with high value in DAT1 let RNG = (channel == 0) ? 4 : 8