0

I'd like to read differential voltage values from an MCP3304 (5v VDD, 3.3v Vref, main channel = 7, diff channel = 6) connected to an RPi 2 b+ as close as possible to the MCP3304's max sample rate of 100ksps. Preferably, I'd get > 1 sample per 100µs (> 10 ksps).

A kind user recently suggested I try porting my code to C for some speed gains. I'm VERY new to C, so thought I'd give Cython a shot, but can't seem to figure out how to tap into the C-based speed gains.

My guess is that I need to write a .pyx file that includes a more-raw means of accessing the ADC's bits/bytes via SPI than the python package I'm currently using (the python-wrapped gpiozero package). 1) Does this seem correct, and, if so, might someone 2) please help me understand how to manipulate bits/bytes appropriately for the MCP3304 in a way that will produce speed gains from Cython? I've seen C tutorials for the MCP3008, but am having trouble adapting this code to fit the timing laid out in the MCP3304's spec sheet; though I might be able to adapt a Cython-specific MCP3008 (or other ADC) tutorial to fit the MCP3304.

Here's a little .pyx loop I wrote to test how fast I'm reading voltage values. (Timing how long it takes to read 25,000 samples). It's ~ 9% faster than running it straight in Python.

# Load packages
import time
from gpiozero import MCP3304


# create a class to ping PD every ms for 1 minute
def pd_ping():
    cdef char *FILENAME = "testfile.txt"
    cdef double v
    # cdef int adc_channel_pd = 7
    cdef size_t i

    # send message to user re: expected timing
    print "Runing for ", 25000 , "iterations. Fingers crossed!"

    print time.clock()

    s = []
    for i in range(25000):
        v = MCP3304(channel = 7, diff = True).value *  3.3
        # concatenate
        s.append( str(v) )

    print "donezo" , time.clock()

    # write to file
    out = '\n'.join(s)
    f = open(FILENAME, "w")
    f.write(out)
Community
  • 1
  • 1
drewbles
  • 65
  • 1
  • 9

1 Answers1

2

There is probably no need to create an MCP3304 object for each iteration. Also conversion from float to string can be delayed.

s = []
mcp = MCP3304(channel = 7, diff = True)
for i in range(25000):
    v = mcp.value *  3.3
    s.append(v)

out = '\n'.join('{:.2f}'.format(v) for v in s) + '\n'

If that multiplication by 3.3 is not strictly necessary at that point, it could be done later.

J.J. Hakala
  • 6,136
  • 6
  • 27
  • 61
  • Incredible! This improved performance by a little over a factor of 10 (from ~ 33 seconds to 2 seconds. Thanks! – drewbles Jul 06 '16 at 20:36
  • Note: If important for posterity, I also removed the multiplication by 3.3 and increased {:.2f} to {:.17f} to glean as much info as possible. Thanks! – drewbles Jul 06 '16 at 20:43
  • Follow-up, any idea how I can declare (via "cdef") the list "s"? I wonder if that might gain me even more speed improvements. – drewbles Jul 06 '16 at 20:58
  • 1
    @drewbles it might be better to use fixed size c or numpy array i.e. something like `cdef uint16_t buffer[25000] ... buffer[i] = mcp.value`. `uint16_t` is available after `from libc.stdint cimport uint16_t`. For a 13-bit ADC `'{:.4f}' .format(v)` would be sensible. – J.J. Hakala Jul 06 '16 at 21:24
  • 1
    That mpc.value seems to be a double between 0 and 1, so `cdef double buffer[25000]`. – J.J. Hakala Jul 06 '16 at 21:37
  • Thanks. Figured out how to employ the buffer[i] method, but doesn't seem to speed up the code much, if at all. Is there a good resource you could point me to for appending an numpy array? I've been reading up on the python website; sounds maybe quicker, but I can't quite wrap my head around defining a numpy in this context. – drewbles Jul 06 '16 at 23:07