Hi all!
I'm working on a project on a Raspberry PI 5 where I need two synchronised PWM's operating with a phase shift. (Software PWM does not cut it for this application I'm afraid.)
My problem is that I cannot get the phase offset working.
In lack of other API's capable of performing this, I've resorted to using mmap to set the registers directly.
Reading the datasheet for RP1, I'm guessing that phase should be respected and pre-loaded in the counter when the PWM channel is enabled, but it's not perfectly clear. Is there anything specific I might have forgotten that need to be setup to get phase offset working?
This is my procedure for configuring the PWMs:And here's the complete runnable test application (that need sudo):
I'm working on a project on a Raspberry PI 5 where I need two synchronised PWM's operating with a phase shift. (Software PWM does not cut it for this application I'm afraid.)
My problem is that I cannot get the phase offset working.
In lack of other API's capable of performing this, I've resorted to using mmap to set the registers directly.
Reading the datasheet for RP1, I'm guessing that phase should be respected and pre-loaded in the counter when the PWM channel is enabled, but it's not perfectly clear. Is there anything specific I might have forgotten that need to be setup to get phase offset working?
This is my procedure for configuring the PWMs:
Code:
# setup IO write((4,0), IO_BANK0_REG(Gpio.GPIO14, IoBankRegister.CTRL), 0) # function a0 for GPIO 14 write((4,0), IO_BANK0_REG(Gpio.GPIO15, IoBankRegister.CTRL), 0) # function a0 for GPIO 15 write((7,7), PADS_BANK0_REG(Gpio.GPIO14), 0) # output enable GPIO 14 write((7,7), PADS_BANK0_REG(Gpio.GPIO15), 0) # output enable GPIO 15 write((5,4), PADS_BANK0_REG(Gpio.GPIO14), 3) # output drive 12 mA write((5,4), PADS_BANK0_REG(Gpio.GPIO15), 3) # setup PWM write((31,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.RANGE), 200000) # range write((31,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.RANGE), 200000) write((31,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.DUTY), 50000) # 25% duty write((31,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.DUTY), 50000) write((31,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.PHASE), 0) # phase has no effect? write((31,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.PHASE), 50000) write((2,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.CTRL), 1) # trailing edge write((2,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.CTRL), 1) write((3,0), PWM_GLOBAL_REG(PwmRegister.GLOBAL_CTRL), 0b1100) # enable PWM channel 2 & 3 write((31,31), PWM_GLOBAL_REG(PwmRegister.GLOBAL_CTRL), 1) # apply config
Code:
from enum import Enumimport mmapimport osimport sysclass Gpio(Enum): GPIO12 = 12 GPIO13 = 13 GPIO14 = 14 GPIO15 = 15class PwmChannel(Enum): CH1 = 0 CH2 = 1 CH3 = 2 CH4 = 3class PwmChannelRegister(Enum): CTRL = 0 RANGE = 1 PHASE = 2 DUTY = 3class PwmRegister(Enum): GLOBAL_CTRL = 0 FIFO_CTRL = 1 COMMON_RANGE = 2 COMMON_DUTY = 3 DUTY_FIFO = 4class IoBankRegister(Enum): STATUS = 0 CTRL = 1FILENAME = "/sys/bus/pci/devices/0000:01:00.0/resource1"PWM0_OFFSET = 0x98000IO_BANK0_OFFSET = 0xd0000IO_BANK0_PADS_OFFSET = 0xf0000REGISTER_SIZE = 4 # in bytesRegister = intBits = tuple[int, int]def PWM_GLOBAL_REG(reg: PwmRegister) -> Register: return PWM0_OFFSET + reg.value * REGISTER_SIZEdef PWM_CH_REG(channel: PwmChannel, reg: PwmChannelRegister) -> Register: return PWM0_OFFSET + 0x14 + channel.value * 0x10 + reg.value * REGISTER_SIZEdef IO_BANK0_REG(gpio: Gpio, reg: IoBankRegister) -> Register: return IO_BANK0_OFFSET + gpio.value * 0x8 + reg.value * REGISTER_SIZEdef PADS_BANK0_REG(gpio: Gpio) -> Register: return IO_BANK0_PADS_OFFSET + gpio.value * REGISTER_SIZE + REGISTER_SIZEdef create_mask(bits: Bits) -> tuple[int, int]: bh, bl = bits mask = ((1 << (bh - bl + 1)) - 1) << bl inv_mask = ~mask & 0xFFFFFFFF return (mask, inv_mask)fd = os.open("/sys/bus/pci/devices/0000:01:00.0/resource1", flags=os.O_RDWR | os.O_SYNC)with mmap.mmap(fd, length=0x400000, access=mmap.PROT_WRITE) as mm: os.close(fd) def write(bits: Bits, reg: Register, value: int) -> None: _, bl = bits mask, inv_mask = create_mask(bits) old_value = int.from_bytes(mm[reg:reg+4], sys.byteorder) new_value: int = old_value & inv_mask | (value << bl) & mask mm[reg:reg+4] = new_value.to_bytes(4, sys.byteorder) # setup IO write((4,0), IO_BANK0_REG(Gpio.GPIO14, IoBankRegister.CTRL), 0) # function a0 for GPIO 14 write((4,0), IO_BANK0_REG(Gpio.GPIO15, IoBankRegister.CTRL), 0) # function a0 for GPIO 15 write((7,7), PADS_BANK0_REG(Gpio.GPIO14), 0) # output enable GPIO 14 write((7,7), PADS_BANK0_REG(Gpio.GPIO15), 0) # output enable GPIO 15 write((5,4), PADS_BANK0_REG(Gpio.GPIO14), 3) # output drive 12 mA write((5,4), PADS_BANK0_REG(Gpio.GPIO15), 3) # setup PWM write((31,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.RANGE), 200000) # range write((31,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.RANGE), 200000) write((31,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.DUTY), 50000) # 25% duty write((31,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.DUTY), 50000) write((31,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.PHASE), 0) # phase has no effect? write((31,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.PHASE), 50000) write((2,0), PWM_CH_REG(PwmChannel.CH3, PwmChannelRegister.CTRL), 1) # trailing edge write((2,0), PWM_CH_REG(PwmChannel.CH4, PwmChannelRegister.CTRL), 1) write((3,0), PWM_GLOBAL_REG(PwmRegister.GLOBAL_CTRL), 0b1100) # enable PWM channel 2 & 3 write((31,31), PWM_GLOBAL_REG(PwmRegister.GLOBAL_CTRL), 1) # apply config
Statistics: Posted by Micke — Wed Jul 31, 2024 12:00 pm