Read multiple ADC channel simultaneously

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
Meide
Posts: 8
Joined: Thu Dec 08, 2016 9:50 am

Read multiple ADC channel simultaneously

Post by Meide » Thu Dec 08, 2016 10:12 am

Hi,
I am currently working in a project where a need to collect data.
I managed to collect data from one of the ADC port that the pyboard has thanks to this code:
import array
import os
import pyb
import utime

adc1=pyb.ADC(pyb.Pin.board.Y12)
buf1=array.array('H',bytearray(30000))
tim=pyb.Timer(6, freq=1000)
adc1.read_timed(buf1,tim)
f1=open('data1.dash','w')
for val in buf1:
s=str(val)
f1.write(s+'\n')
f1.close()

This code enables to read the values of the port Y12 and stock them in a file in the SD card.
I am now trying to do the same but with two ports. But if I use the same code, that will first collect the data from the first port and then collect the data from the second port. Is there a way to collect both data at the same time ?
Thanks for your help

Meide

User avatar
kamikaze
Posts: 154
Joined: Tue Aug 16, 2016 10:10 am
Location: Latvia
Contact:

Re: Read multiple ADC channel simultaneously

Post by kamikaze » Thu Dec 08, 2016 7:05 pm

never tried to work with ADC, but I would try following way:

1. set callback function for a timer that will set a flag e.g. time_to_read_values
2. in main loop check for time_to_read_values (don't forget to sleep for a while in the end of the loop)
3. if time_to_read_values flag is set = just read both sequentially and write into file.

Also take a look to ADC channel... does it make life easier? (dunno)

and yes, I would suggest you to use context manager for file stuff like:

Code: Select all

with open(...) as f:
    print(s, file=f) #in this case there is no need to concatenate with '\n'

Meide
Posts: 8
Joined: Thu Dec 08, 2016 9:50 am

Re: Read multiple ADC channel simultaneously

Post by Meide » Thu Dec 15, 2016 3:14 pm

Hi,
Thank you for your answer. Sorry for taking so much time to answer but I come with new things.
Here is the code that I finnaly used:

import array
import os
import pyb
import utime

buf1=array.array('H',bytearray(10000))
buf2=array.array('H',bytearray(10000))
temps=array.array('L',bytearray(20000))
start=utime.ticks_us()
i=0

while i!=5000:
adc2=pyb.ADC(pyb.Pin.board.Y11)
adc1=pyb.ADC(pyb.Pin.board.Y12)
buf1[i]=adc1.read()
buf2[i]=adc2.read()
temps[i]=utime.ticks_us()
i=i+1

end1=utime.ticks_us()

f=open('chanel1.dash','w')

for val in buf1:
s1=str(val)
f.write(s1+'\n')

f.close()
h=open('chanel2.dash','w')

for val in buf2:
s2=str(val)
h.write(s2+'\n')
h.close()

j=open('t.dash','w')
for val in temps:
s3=str(val)
j.write(s3+'\n')
j.close()

end2=utime.ticks_us()

r=open('t1.dash','w')
s4=str(end1-start)
s5=str(end2-start)
r.write(s4+'\n'+s5)
r.close()

So this code enables first to collect all the data and then to stock them in different files. There are one file for the first ADC port (f), one file for the second ADC port (h), one file which contain time for each loop of the "while loop" (j), and a file which containt the time of execution of the "while loop" and the total time of the operation (reading+writing).
After collecting all these data, I began to analyze them and I found several problems...
First, If we make a graph of what we measure in one channel as a function of time, we can see that the results are very strange (I am sending the same triangular signal to the two ports). Here are the graph for the 1200 first value collected:
http://zupimages.net/viewer.php?id=16/50/2fvz.jpg
http://zupimages.net/viewer.php?id=16/50/vo59.jpg
http://zupimages.net/viewer.php?id=16/50/1xer.jpg
As you can see, at the begining the values are collectinf very fast and then it goes slower until a big gap.. And then it begins again.
You can also see that after a certain number of iterations, points which are totally unclear appear.

An other thing really strange, if we look at the gap between the 2 channels (which are receiving the same signal), we will see most part of the time that the gap between the two channels is small. But sometimes we can see big error as you can see in this graph :
http://zupimages.net/viewer.php?id=16/50/y8cm.jpg
(graph of: |Channel1-Channel2| vs Time )

Do you have an idea of what can explain all these errors that I have ?

Some specifications:
-time is in us
-we measure the voltage received by the pyboard which is in mV
-I used both "For loop" and "while loop", problem is the same
-I tried to use your command for writting file and it wasn't working

I know that may be I wasn't clear on some point, so please if you don't understand something just ask me.
I will try to use the same loop for only one channel to see if I have the same error and I will post here my results.

Thank you.

Médé

User avatar
kamikaze
Posts: 154
Joined: Tue Aug 16, 2016 10:10 am
Location: Latvia
Contact:

Re: Read multiple ADC channel simultaneously

Post by kamikaze » Thu Dec 15, 2016 5:55 pm

Meide wrote:Here is the code that I finnaly used:
Don't you want to place code into

Code: Select all

 tags? And restore identitation? :roll:
Last edited by kamikaze on Thu Dec 15, 2016 6:22 pm, edited 1 time in total.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Read multiple ADC channel simultaneously

Post by pythoncoder » Thu Dec 15, 2016 6:05 pm

@kamikaze The option isn't available to new posters as a defence against spam.

@Meide A few points. There is no need to initialise the ADC's in a loop. It's more efficient to initialise arrays with a generator. And, as @kamikaze says, it's best to use context managers for files.

The big issue is whether to grab samples in a free running loop, as you are doing, or whether to use a timer. A timer will give consistent sampling intervals but won't be able to sample above about 10KHz. Your method will be faster, but the interval between samples will vary because interrupts will occur at times.

The following keeps your free running approach but addresses the issues above.

Code: Select all

import array
import os
import pyb
import utime
NSAMPLES = 5000
buf1 = array.array('H', (0 for _ in range(NSAMPLES)))
buf2 = array.array('H', (0 for _ in range(NSAMPLES)))
temps = array.array('L', (0 for _ in range(NSAMPLES)))
adc2=pyb.ADC(pyb.Pin.board.Y11)
adc1=pyb.ADC(pyb.Pin.board.Y12)

start=utime.ticks_us()

for i in range(NSAMPLES):
    buf1[i] = adc1.read()
    buf2[i] = adc2.read()
    temps[i] = utime.ticks_us()

end1=utime.ticks_us()

with open('chanel1.dash','w') as f:
    for val in buf1:
        s1 = str(val)
        _ = f.write(s1+'\n')

with open('chanel2.dash','w') as h:
    for val in buf2:
        s2 = str(val)
        _ = h.write(s2+'\n')

with open('t.dash','w') as j:
    for val in temps:
        s3 = str(val)
        _ = j.write(s3+'\n')

end2=utime.ticks_us()

with open('t1.dash','w') as r:
    s4=str(end1 - start)
    s5=str(end2 - start)
    _ = r.write(s4+'\n'+s5)
(EDITED 16th Dec)
Last edited by pythoncoder on Fri Dec 16, 2016 6:02 am, edited 2 times in total.
Peter Hinch
Index to my micropython libraries.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Read multiple ADC channel simultaneously

Post by Roberthh » Thu Dec 15, 2016 7:43 pm

If you want to read samples at a fixed rate, you can use read_timed, like explained here.
http://docs.micropython.org/en/latest/p ... read_timed
Very simple.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Read multiple ADC channel simultaneously

Post by pythoncoder » Fri Dec 16, 2016 5:56 am

@Roberthh One issue with read_timed is that it only supports a single ADC. To sample two ADC's you'd need to issue two read_timed statements. This could lead to phase errors between the channels. There is a need for a means of reading two ADC's as near simultaneously as possible and at a precise frequency and, as far as I'm aware, the firmware doesn't support it.

I hit this problem with my experiments with making an electrical network analyser. I used a few tricks to get reasonable phase accuracy but this was only possible because the signal driving the network was available. Getting reasonable phase accuracy from a fast arbitrary incoming signal is difficult.
Peter Hinch
Index to my micropython libraries.

cadcam
Posts: 2
Joined: Thu Dec 08, 2016 3:45 pm

Re: Read multiple ADC channel simultaneously

Post by cadcam » Fri Dec 16, 2016 10:02 am

@Pythoncoder, - Fully agree, see a similar question I raised

viewtopic.php?f=2&t=2682

It seems a real shame that when running without an operating system underneath that micropython doesn't natively give the option to collect multichannel data fast and accurately. It is one of the potential attraction of the pyboard/wipy etc over the likes of RPi for 'fast...ish' real time control.

Regards

Meide
Posts: 8
Joined: Thu Dec 08, 2016 9:50 am

Re: Read multiple ADC channel simultaneously

Post by Meide » Fri Dec 16, 2016 11:52 am

Hi,
First thank you for all your answers, that help me a lot.

So, I tried the code that you post before. I did several attemps to be sure of the result.
Here are the different results that I had:

Chan1 vs Time and Chan 2 vs Time (2 attempts) :
http://zupimages.net/viewer.php?id=16/50/h0xq.jpg
http://zupimages.net/viewer.php?id=16/50/w328.jpg

Chan1 vs Time, 1 to 1000 data:
http://zupimages.net/viewer.php?id=16/50/lc5f.jpg
http://zupimages.net/viewer.php?id=16/50/dfcr.jpg

Chan 1 vs Time, 1000 to 1500 data:
http://zupimages.net/viewer.php?id=16/50/d60z.jpg
http://zupimages.net/viewer.php?id=16/50/sghv.jpg

So as you can see, the results are really better than before at the beginning and then we have a degradation (with some gaps).
I am really surprised to have these gaps because at the beginning it looks regular and clean..

And I also tried with only one channel with the same loop:
http://zupimages.net/viewer.php?id=16/50/isoo.jpg
We have the same degradation, so the gaps certainly come from the loop...

So I am thinking to put a timer to have more regular results (as you said @Pythoncoder). But I was really surprised when you said a timer could sample at 10KHz. The signal that I am using for the moment is only 150Hz and, as you can see, I have some difficulties to obtain something accurate. And the method that I am using for the moment should be faster than to put a timer no ? So how the Timer method can support 10KHz signal ?

If you have some ideas to improve the code or the method, don't hesitate to tell me and I will make a try and post results here.
If you need more information or data, just ask me.

Thank you again

Meide

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Read multiple ADC channel simultaneously

Post by pythoncoder » Fri Dec 16, 2016 4:43 pm

I'm afraid I'm not sure what your graphs represent. Consider this code which uses the timer to read the ADC's. This is the best way to ensure consistent timing:

Code: Select all

import array
import os
import pyb
import utime
import micropython
micropython.alloc_emergency_exception_buf(100)

NSAMPLES = 5000
FREQ = 10  # Hz

buf1 = array.array('H', (0 for _ in range(NSAMPLES)))
buf2 = array.array('H', (0 for _ in range(NSAMPLES)))
temps = array.array('L', (0 for _ in range(NSAMPLES)))

adc2=pyb.ADC(pyb.Pin.board.Y11)
adc1=pyb.ADC(pyb.Pin.board.Y12)
dac1 = pyb.DAC(1) # Pin X5
tim = pyb.Timer(4)

def sawtooth():
    for i in range(256):
        yield i
    while i > 0:
        i -= 1
        yield i

bufout = bytearray(sawtooth())
dac1.write_timed(bufout, FREQ * len(bufout), mode=pyb.DAC.CIRCULAR)

i = 0
def cb(timer):
    global i
    buf1[i] = adc1.read()
    buf2[i] = adc2.read()
    temps[i] = utime.ticks_us()
    i += 1
    if i >= NSAMPLES:
        timer.deinit()

tim.init(freq=5000, callback=cb)

start = utime.ticks_us()
while i < NSAMPLES:
    pass

end1 = utime.ticks_us()
print((end1 - start) // NSAMPLES)
maxd = 0
for n, s in enumerate(buf1):
    if n > 0:
        maxd = max(maxd, abs(s - buf1[n -1]))
print('Maximum difference between consecutive samples: {} theoretical: {}'.format(maxd, 4096//256)) 

with open('chanel1.dash','w') as f:
    for val in buf1:
        s1 = str(val)
        f.write(s1+'\n')

with open('chanel2.dash','w') as h:
    for val in buf2:
        s2 = str(val)
        h.write(s2+'\n')

with open('t.dash','w') as j:
    for val in temps:
        s3 = str(val)
        j.write(s3+'\n')

end2=utime.ticks_us()

with open('t1.dash','w') as r:
    s4=str(end1 - start)
    s5=str(end2 - start)
    r.write(s4+'\n'+s5)
I ran this (with a link between X5 and Y12) then plotted the content of chanel1.dash. This was the outcome:
Image
Peter Hinch
Index to my micropython libraries.

Post Reply