Closed digixx closed 3 years ago
Would you mind posting a picture or screenshot of the oscilloscope? That'll show the issue in a second way.
Yellow => DAC Signal Blue => Trigger Helper Sgnal
This is the waveform with level_high = 32767
Waveform with high_level = 32768
Waveform with high_level = 65535
If you run the program posted above, you will see that at some high_levels values the waveform changes randomly the height of the pulses
Ok, thanks for the images! I'm not sure what the issue is and am not sure when we'll have time to look at it. Let me know if you'd like to help debug it. I'm happy to give pointers.
I am happy to help debuging this issue.
What interests me most, how is the DAC initialized and in which mode it is operated. Is this software part of open source?
As a check, I had run this code, The waveform looks ok.
analog_out = AnalogOut(board.A0)
while True:
for i in range(0, 65535, 64):
analog_out.value = i
Maybe the DAC is not operated in the same mode for DMA-output and AnalogOut()
Awesome! Yes, it is all open source.
AnalogOut is not DMA'd and the source is here: https://github.com/adafruit/circuitpython/blob/main/ports/atmel-samd/common-hal/analogio/AnalogOut.c
AudioOut is DMA'd and the source is here: https://github.com/adafruit/circuitpython/blob/main/ports/atmel-samd/common-hal/audioio/AudioOut.c
Build instructions for CircuitPython are here: https://learn.adafruit.com/building-circuitpython
The discord is a good place to chat with us too. https://adafru.it/discord in the #circuitpython channel
I think this is same issue as Adafruit Learn: SAMD51 M4 DAC has problems getting it up.
Here is a video about the waveform counting from 1000 to 65535
https://www.dropbox.com/s/9r620vbzrwsiiid/MAH01524_2.mp4?dl=0
Hi there,
Today I did some tests with the DAC.
This is the software I use making the tests
import audiocore
import audioio
from analogio import AnalogOut
import board
import time
import digitalio
import array
# Trigger signal for oscilloscope
Trigger = digitalio.DigitalInOut(board.D4)
Trigger.direction = digitalio.Direction.OUTPUT
dac = audioio.AudioOut(board.A0)
def play_sequence(sequence):
data2send = audiocore.RawSample(sequence, sample_rate = 1000)
dac.play(data2send, loop = False)
while dac.playing:
Trigger.value = True
Trigger.value = False
time.sleep(0.01)
def create_static_sequence():
return array.array('H',[16384, 0, 2000])
while True:
play_sequence(create_static_sequence())
The DAC RawSample() array contain three values: 16384, 0, 2000 This array is played every 10ms. While dac.playing is True, digital pin D4 is set to high.
Beginning with the DAC init glitch, the output of the DAC rises to value 0x8000. This happens when the DAC is initialised by dac.play(). During while dac.playing is true, Trigger.value is set to high.
It can be seen that during the first sample-rate-period the value of quiescent_value is set. One ms later the first value (16384) of the RawSample() array is set. Another one ms later the value of 0 follows. The falling signal of Trigger.value shows the end of playing.
But where is the last value of 2000 showing up? Look at the next picture.
When Trigger goes high, the last played value (0) is showed on output for one period like the quiescent_value after the DAC initialisation. It looks like buffer handling misses the last value and reaches the end one value later on the next cycle.
Using DMA on DAC is a very fine feature to create precisely timed artificial waveform for all kind of purposes. Ramping up and down the DAC output to a static value does not make a sence to me. It will always jump from the initial level to the first in the array. I would recommend to init the DAC quiescent_value to the first value of the array. After the array is played, keep the last value.
This picture shows the signals when the software is restarted (aka saving the file)
How can I debug the C code during runtime? In CircuitPython I can use print().
Ramping up and down the DAC output to a static value does not make a sence to me. It will always jump from the initial level to the first in the array. I would recommend to init the DAC quiescent_value to the first value of the array. After the array is played, keep the last value.
I'd be ok with this change. That sounds reasonable.
How can I debug the C code during runtime? In CircuitPython I can use print().
In C you can do mp_printf(&mp_plat_print, <format>, <variables>);
. I generally use GDB if that isn't enough. My process is documented here: https://learn.adafruit.com/debugging-the-samd21-with-gdb
Hi,
Now, after many hours of source code readings, trying some debug code, I have to say that I am lost. I never wrote much code in C. Mostly some small Arduino code running the bare metal. But the code base for CircuitPython is using so much wrapper code to have all the boards supported is killing me. Compared to one of my very first software on a C64, printing Mandelbrot fractals on the screen, this is beyond my brain.
Back to the topic
If a RawSample() is played, only the first cycle has all the problems documented in the earlier posts. When running with "loop=True" you cannot see it in the following output cycles.
array.array('H',[0, 45000, 20000, 3000])
At the very first beginning of my project, I wanted to create a self calculated waveform in python, played over the DAC to a radio sending digital data. Then i found out that the memory wasn't sufficient for 3 seconds of sound. Then I outsourced the waveform generation to an ATMEL Tiny 412 Controller. Now the CircuitPython board only has to calculate the bits for the data, not the waveform itself. Because there are no timer interrupts available in CircuitPython, I tryed to use audiocore.RawSample() to send timed signals to the 412 generating the waveform. And this is my problem: The first cycle is corrupt. But my project only needs one shot.
If I use AnalogOut(board.A0) it works like a charm, but I have no timed events at hand to set the next value at the right time.
@digixx As long as CIRCUITPY isn't mounted by a host over USB maybe you could write the data to a wav file and then play that back?
@kevinjwalters Yes, technically possible, but is it worth? I dont think so. Out of curiosity I will create a defined wav file and see if it is played correctly.
Oh, I was just talking about addressing the size (> memory) not the "fidelity" of the output. The SAMD51 DACs just seem to have a lot of strange behaviour. Asking the manufacturer would seem to be the next step here with some simple example C code.
HEUREKA!
We have here different problems like wrong signal levels and missing the last value of the array. I noticed that small changes in values are better converted than a jump from 0 tp 65535. Creating an sample array containing several hunderts of 0,65535,0,... I observed that when a level was low the next level was higher than the average max level. Touching the wire of A0 also injected some noise which can be seen on the scope. Energie problem?
Yes. In Circuitpython the max sample rate is limited to 1M. The SAM D5X data sheet shows some setting for the DAC at 47.6.9.2 Output Buffer Current Control. In the source the register is set with DAC_DACCTRL_CCTRL_CC100K instead of DAC_DACCTRL_CCTRL_CC12M. With this setting the output is like it has to be. Full swing from 0 to 65535 without any power problems.
I think 1 Msps is the physical limit of the hardware? And is this the same DAC setting as the one in https://github.com/adafruit/circuitpython/issues/2099#issuecomment-526865939 and Adafruit Forums: What's fastest practical sample_rate for M4 DACs?? Oh, re-reading that forum post it looked like CircuitPython had and maybe still has a lower max sample rate, I'd forgotten about that.
How are you changing the hardware settings? In C code or recompiling CircuitPython?
I changed the code in \circuitpython\ports\atmel-samd\common-hal\audioio\AudioOut.c
starting at line 189
if (channel0_enabled) {
#ifdef SAMD21
DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI;
// We disable the voltage pump because we always run at 3.3v.
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_AVCC |
DAC_CTRLB_LEFTADJ |
DAC_CTRLB_EOEN |
DAC_CTRLB_VPD;
#endif
#ifdef SAM_D5X_E5X
DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI0;
DAC->DACCTRL[0].reg = DAC_DACCTRL_CCTRL_CC12M |
DAC_DACCTRL_ENABLE |
DAC_DACCTRL_LEFTADJ;
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPU;
#endif
}
#ifdef SAM_D5X_E5X
if (channel1_enabled) {
DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI1;
DAC->DACCTRL[1].reg = DAC_DACCTRL_CCTRL_CC12M |
DAC_DACCTRL_ENABLE |
DAC_DACCTRL_LEFTADJ;
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPU;
}
Then I recompiled it getting the .uf2 file and loaded it to the board. The code does check if the calculated sample rate exceeds 1M and throws an execption (line 375)
@digixx Please make a PR! That's an awesome find!
@tannewt Can you do it for me? I have problems commit a request do to GPG issues on Ubuntu.
@digixx I can as a last resort. You can do it through the github website though: https://docs.github.com/en/free-pro-team@latest/github/managing-files-in-a-repository/editing-files-in-your-repository
Ok, I will try. This is all new to me, so please be patient.
I am using a Feather M4 Express to send a sequence of voltage-levels (bits) at a defined baudrate. Because of lack of timer interrupts, the function audiocore.RawSample() seems to fill the gap using digital to analog conversion on A0.
But a simple test pattern (squarewave) showed some strange behavior. Just run the code below and watch the DAC (A0) pin on your oscilloscope. In this example I use Pin D4 as trigger signal to sync the OSC.
The very last value of the sequence is not present at the end of the waveform but on the beginning of the next sequence. Sending it twice solves this.