DMA glitch? #17890
-
Hello everyone, I’m seeing odd behavior on a Raspberry Pi Pico H (RP2040) running MicroPython 1.25.0. I am trying to generate a sine wave with variable frequency using PWM and DMA. What the code do:
For a long time I couldn’t get a clean sine after the RC filter; then, without after randomly adding some lines, it suddenly looked fine on the scope (see picture below for the setup and the sine). I was quite happy, I can vary the sine period from 10–1000 Hz just fine. However, if I comment out a single line—or even change which library I import—the filtered output changes dramatically. Sometimes the sine is distorted or disappears. The exact waveform seems to depends on which line/module is present, and also the MicroPython version, for example the picture below shows how it look on MicroPython 1.26.00 I’m trying to understand what could cause this intermittent behavior. Could you recommend some approaches? Here below is reported the script and here can be found the mcp4728.py and utils.py import math, rp2
from machine import Pin, PWM, I2C
from uctypes import addressof
from array import array
import machine
import utils
import mcp4728
import time
# i2c = I2C(0,scl=Pin(9),sda=Pin(8),freq=400_000) # Define I2C pins
# time.sleep(1)
# # --- User configuration for 1kHz sine in 100kHz PWM ---
LED_PIN = 12 # GPIO pin driving your LED (via MOSFET, etc.)
SINE_FREQ = 200 # Desired sine wave frequency (Hz)
SINE_SAMPLES = 2**8 # Number of samples per sine period (power of two recommended)
PWM_CARRIER = SINE_FREQ * SINE_SAMPLES # PWM carrier frequency (Hz), much higher than SINE_FREQ
slice_idx = LED_PIN // 2
CC_OFFSET = 0x0C
DIV_OFFSET = 0x04
TOP_OFFSET = 0x10
CC_ADDR = 0x40050000 + slice_idx * 0x14 + CC_OFFSET
DIV_ADDR = 0x40050000 + slice_idx * 0x14 + DIV_OFFSET
TOP_ADDR = 0x40050000 + slice_idx * 0x14 + TOP_OFFSET
# Configure the hardware PWM slice
pwm = PWM(Pin(LED_PIN))
pwm.freq(PWM_CARRIER) # set carrier frequency
pwm.duty_u16(2**16 //2) # initiliaze in 50% duty-cycle
top_value =machine.mem32[TOP_ADDR]
# Build a 16‑bit sine lookup table for 1kHz in 100kHz envelope
num_samples = SINE_SAMPLES#int(SAMPLE_RATE // SINE_FREQ) # 100 samples for 1kHz@100kHz
sine_buf = array('h', (0 for _ in range(num_samples)))
for i in range(num_samples):
# sin() → [–1..1] → [0..1] → [0..65535]
# v = (math.sin(2 * math.pi * i / num_samples) + 1) * 0.5 # 0..1
v = (math.sin(2 * math.pi * i / num_samples + math.pi/2) + 1) * 0.5
sine_buf[i] = int(round(v * (top_value-1))) # normalize to the TOP_ADDR value
# Setup the DMA, pick the right DREQ for “PWM slice wrap”
DREQ_PWM_WRAP6 = 30 # DREQ_PWM_WRAP6
# --- Single-channel circular DMA for jitter-free PWM sine output ---
buf_bytes = num_samples * 2 # 2 bytes per 'H' sample
ring_size = int(math.ceil(math.log2(buf_bytes)))
# Use the address for channel A (no offset), and ensure DMA is triggered
dma = rp2.DMA()
ctrl = dma.pack_ctrl(
size=1, # 1 = 16-bit transfers
inc_write=False, # always write to the same CC register
inc_read=True, # advance through the sine_buf
ring_size=ring_size, # wrap at 2^ring_size bytes
ring_sel=False, # wrap the read address (sine_buf)
treq_sel=DREQ_PWM_WRAP6
)
dma.config(
read=sine_buf,
write=CC_ADDR,
count=0xFFFFFFFF, # continuous transfer
ctrl=ctrl,
trigger=False
)
dma.active(1) |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
If you use a DMA ring buffer, you need to make sure that it starts at a correct address. |
Beta Was this translation helpful? Give feedback.
-
One possible source of confusion is that DMA is not stopped by a soft reset. I found I had to hard reset or power cycle the board. The answer is to explicitly stop it on exit. |
Beta Was this translation helpful? Give feedback.
-
Thanks @GitHubsSilverBullet and @peterhinch for the explanations! BUFFER_SIZE = 2**8
placeholder = array('H', (0 for _ in range(512*2+2)))
_base = uctypes.addressof(placeholder) # get base address
_end_base = _base + len(placeholder)-1
_masked = _base & ~(511)
_offset = _masked + 512
buffer = uctypes.bytearray_at(_offset, BUFFER_SIZE*2)
for i in range(BUFFER_SIZE):
v = (math.sin(2 * math.pi * i / BUFFER_SIZE + math.pi/2) + 1) * 0.5
value = int(round(v * (top_value-1)))
struct.pack_into('H', buffer, i*2, value)
_end_buffer = uctypes.addressof(buffer) + len(buffer)
assert _end_buffer < _end_base # assert space allocation
assert uctypes.addressof(buffer)%512 == 0 # assert alignment |
Beta Was this translation helpful? Give feedback.
Thanks @GitHubsSilverBullet and @peterhinch for the explanations!
I sedimented the information, and now the code works.