Page 1 of 1

Odd delays when generating waveforms with SPI MCP4802

Posted: Sun Feb 16, 2020 9:41 pm
by BillAZ
I did a quick search and didn't see anything similar to this but I may have missed it. I'm using the SPI bus on the Pyboard v1.1 to talk to a MCP4802 12 bit D/A converter. I am currently generating great saw-tooth, triangle and sine waves but when I view the output on an oscope I can see a random delay of around 37ms that causes a lot of jitter in the waveform. I'm not using interrupts in my program but it looks like something is causing an interrupt like perhaps a timer. I get several of these within a few seconds and them maybe 10 seconds or so without any anomalies.

What's the easiest way to tell is something is generating an interrupt? Any ideas what could be causing this if not interrupts? :?:

Re: Odd delays when generating waveforms with SPI MCP4802

Posted: Sun Feb 16, 2020 11:56 pm
by jimmo
Could be garbage collection?

Try using

Code: Select all

import gc

gc.collect()
gc.disable()
# sensitive code here
gc.enable()

Re: Odd delays when generating waveforms with SPI MCP4802

Posted: Mon Feb 17, 2020 2:53 am
by BillAZ
When I turn off the garbage collection I get an immediate memory error. I'm using bytearrays to hold the two bytes to send to the D/A and each time I'm clearing it and creating a new array, this could explain the reason it needs to collect the trash so often. This was just intended to be test code to try out the Pyboard SPI interface which is working perfectly. Here is the code:

Code: Select all

from pyb import SPI, Pin
from utime import sleep_ms, sleep_us
from math import sin, pi
from struct import pack

spi = SPI(1)
spi.init(SPI.MASTER, polarity=0)
cs = Pin('X22', Pin.OUT)  # Grey wire
cs.value(1)
ldac = Pin('X21', Pin.OUT)  # Yellow
ldac.value(1)
scopeTrigger = Pin('Y12', Pin.OUT)
scopeTrigger.value(1)

buf = bytearray()
buf.append(0x3f)
buf.append(0x3f)


def setDAC(buffer):
    cs.value(0)
    spi.send(buffer)
    cs.value(1)
    # sleep_us(1)
    ldac.value(0)
    sleep_us(1)
    ldac.value(1)


def saw():
    global buf
    while True:
        scopeTrigger.value(0)
        for x in range(16):
            for y in range(0xFF):
                buf = bytearray()
                buf.append(x + 0b00110000)  # Select ch A, 1x Gain, enable output A
                buf.append(y)
                setDAC(buf)
                sleep_us(5)
            scopeTrigger.value(1)

def tri():
    global buf
    while True:
        scopeTrigger.value(0)
        for x in range(16):
            for y in range(0, 0xFF, 128):
                buf = bytearray()
                buf.append(x + 0b00110000)  # Select ch A, 1x Gain, enable output A
                buf.append(y)
                setDAC(buf)
                sleep_us(5)
        scopeTrigger.value(1)
        for x in range(16):
            for y in range(0, 0x100, 128):
                buf = bytearray()
                buf.append((0x0F - x) + 0b00110000)
                buf.append(0xFF - y)
                setDAC(buf)
                sleep_us(5)


def sine():
    global buf
    while True:
        scopeTrigger.value(0)
        for x in range(500):
            value = (pi * 2) * (x / 500)
            value = sin(value)
            total = (value * 0xFFF * 0.5) + (0xFFF / 2)
            b2, b1 = pack(">H", int(total))
            buf = bytearray()
            buf.append(b2 + 0b00110000)
            buf.append(b1)
            setDAC(buf)
            sleep_us(5)
            if x == 50:
                scopeTrigger.value(1)
I don't have many comments but it's pretty straight forward. I'm sure it could be a lot cleaner, I'm a C programmer for a lot of years and I'm still trying to get my arms around all the fantastic structures in Python.

BTW: I'm using PyCharm to write the code and run the Repl.

Re: Odd delays when generating waveforms with SPI MCP4802

Posted: Mon Feb 17, 2020 2:19 pm
by BillAZ
I just completed some experiments: First I changed my handling of the bytearray so that I don't keep initializing it:

Code: Select all

# old method, this code was used every time I needed to send a new command to the MCP4802
buf = bytearray()
buf.append(x)
buf.append(y)
# new method
buf = bytearray(b'\x00\x00') #initialized once at the start of the routine only
# in the wave generator functions I use
buf[0] = x
buf[1] = y
With this change I can run the saw and triangle wave routines without needing garbage collection. The sine wave routine uses the math library and with garbage collection turned off I can't run that routine, it gets a memory error before I can even capture a wave on the scope. All that aside, even with garbage collection off I'm getting the anomalies, about 20ms now, so while this did improve the stability of the output it isn't the only issue.