Shop OBEX P1 Docs P2 Docs Learn Events
I2c driver MCP3426 — Parallax Forums

I2c driver MCP3426

DigitalBobDigitalBob Posts: 1,487
edited 2015-06-28 15:27 in Propeller 1
Greetings everyone;
Has anyone ever used the MCP3426, 16 bit ADC I'm looking for a spin driver for that device.

Thanks to everyone in advance

Bob
«1

Comments

  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-06-25 12:47
    I'm not aware of a Spin driver for the MCP3426. I'm hoping one of the other I2C ADC drivers won't be hard to modify to use with this chip. I added three of these chips to my last Digi-Key order and I plan to figure out some sort of driver in the future but I don't have time right now to work on this.

    If you find something that works I hope you let us know.
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-25 14:07
    There was very recently code posted for the MCP3428 on this very forum. MCP3426 shares a data sheet and I'm thinking it would be VERY similar. Also I recently posted a DS3232 driver that included an MCP3428 add-on.
  • caskazcaskaz Posts: 957
    edited 2015-06-25 16:53
    Hi.
    I had wrote Forth-code for MCP3425 in last year .
    #444 in [Propforth v5.5 is available for download]thread.
    Not spin.
    MCP3425 is like MCP3426.
    MCP3425 is 1channel A/D and don't have reference voltage.
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-25 17:18
    Some good leads I tried the one with the DS3232 but I could not isolate just the MCP3428 section of it and I have not dabbled in the prop forth yet.

    Thanks
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-26 08:48
    Here is a link the original MCP3428 thread. There is a pretty much complete JonnyMac driver in post #11. By the way, reading this thread from beginning to end is a useful exercise. It is illustrative, if not typical, of the dynamics in this forum.

    http://forums.parallax.com/showthread.php/160950-Need-code-for-MCP3428-Please

    Here is a link to another approach. See post #14. You need the jm PASM driver from post #2.

    http://forums.parallax.com/showthread.php/161040-Another-i2c-assembly-driver
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-26 11:41
    No luck I tried most of those examples and nothing. I tried the ADC input example and I at least get some talking on the SDA, SCL lines and some bits back
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-26 11:51
    Well, it is at this point where we ask that you post details: Schematic or at least wiring information, actual code (between
    and
    
    commands, and results. Promise that MCP342x's can be made to work!
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-26 12:42
    CON
    _clkmode = xtal1 + pll16x 'clock mode
    _xinfreq = 5_000_000 '* crystal frequency


    CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq

    'MCP3428 ADC Values
    SCL = 17 ' Clock MCP3428 Pin 8
    SDA = 18 ' Data In/Out MCP3428 Pin 7

    OBJ
    SN : "Simple_Numbers"
    'pst : "Parallax Serial Terminal Plus"
    DEBUG : "FullDuplexSerial"

    adc : "jm_mcp342x"

    VAR
    Byte DataH, DataL, DataC

    PUB Main
    ' pst.Start(57600)
    DEBUG.start(31, 30, 0, 9600)

    'MCP3428 Initialized
    adc.startx(SCL, SDA, 0, 0)
    ' adc.set_channel(0,0)
    adc.set_channel(0,16,1)

    repeat
    DataH := adc.raw2mv(0,16,1)
    DataL := adc.raw2mv(1,16,1)
    DataC := adc.raw2mv(2,16,1)

    DEBUG.tx($D)


    DEBUG.str(string("Data H... "))

    DEBUG.Dec(DataH)
    DEBUG.tx($D)

    'pst.Position(1, 16)
    ' pst.Dec(DataL)
    'pst.Position(1, 22)
    'pst.Dec(DataC)
    ' pst.Str(String(" "
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-26 12:46
    I'm not getting any signal on SDA or SCL. I'm using 10k pull-up's, returns "0" on debug
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-26 12:49
    MCP3426 pin 4 to pin 18 prop and MCP3426 pin 5 to pin 17 prop, 10K pull-up's to 3.3 volts one for each pin
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-26 13:35
    Um, the code you are using is all JonnyMac stuff; I only ever used his PASM driver from

    http://forums.parallax.com/showthread.php/161040-Another-i2c-assembly-driver.

    That being said, I think after you do the adc.start, your loop should look like:
    repeat
       adc.set_channel(0, adc#SAMP_16, ADC#GAIN_1)    'start the conversion
      ' myrawdata := adc.read_raw   unneccesary.  no idea why I put this here     
       repeat until adc.ready==true   'wait until it is done
       myrawdata := adc.read_raw   'now read it raw data
       millivolts := adc.raw2mv(myrawdata...    'convert raw to millivolts
    

    You should define myrawdata and millivolts both as longs.

    JonnyMac or CelticLord or DuaneD, feel free to jump in here anytime. I'm a little out of my depth; I just ever used JonnyMac's PASM I2C driver.

    Edit: I removed the first adc.read_raw call.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-06-27 12:31
    Thanks for the hints Tom.

    I tried both Tom's demo code from the other thread and the code below without luck.
    CON                                
    
      _clkmode = xtal1 + pll16x   
      _xinfreq = 5_000_000
    
    OBJ
    
      Pst : "parallax serial terminal"
      Adc : "jm_mcp342x"
      Format : "StrFmt"
      
    PUB Main
                                           
      Pst.Start (115_200)
      
      repeat
        result := Pst.rxcount
        Pst.str(String(11, 13, "Press any key to start."))
        waitcnt(clkfreq / 10 + cnt)
      until result
    
      Pst.RxFlush
    
      Pst.Clear
    
      Pst.home
      Pst.str(String(11, 13, "Starting ADC Driver"))      
    
      Adc.start(adc#F, adc#F)
    
      repeat
         Pst.str(String(11, 13, "Set ADC Channel")) 
         Adc.set_channel(0, Adc#SAMP_16, Adc#GAIN_1)
         
         Pst.str(String(11, 13, "Waiting for Ready")) 
         repeat until Adc.ready == true   'wait until it is done
         result := Adc.read_raw
         Pst.str(string(11, 13, "Channel 1 raw =  "))
         Pst.dec(result)
         Pst.str(string(" = "))
         ReadableBin(result, 32)
         
         waitcnt(clkfreq + cnt)
    
    PUB ReadableBin(localValue, size) | bufferPtr, localBuffer[12]    
    '' This method display binary numbers
    '' in easier to read groups of four
    '' format.
    
      bufferPtr := Format.Ch(@localBuffer, "%")
      
      result := size // 4
       
      if result
        size -= result
        bufferPtr := Format.Bin(bufferPtr, localValue >> size, result)
        if size
          bufferPtr := Format.Ch(bufferPtr, "_")
      size -= 4
     
      repeat while size => 0
        bufferPtr := Format.Bin(bufferPtr, localValue >> size, 4)
        if size
          bufferPtr := Format.Ch(bufferPtr, "_")  
        size -= 4
        
      byte[bufferPtr] := 0  
      Pst.Str(@localBuffer)
    

    Here's the output:
    Starting ADC Driver
    Set ADC Channel
    Waiting for Ready
    

    As can bee seen from the output, the code never gets past waiting for ready.

    My first guess is I didn't wire something correctly (if any of you saw my PCB you'd likely agree). I'm hoping someone could check over the demo code I used for Jon's object to see if did something wrong.

    Edit: I'm not sure if I'm using the correct parameters with the start call. I'm using float on each address bit but I'm not sure this is correct. I have a few more things to try.
    Edit again: Sorry for not including the child objects to the above program. The archive attached to post #14 includes the child objects used by both this program and my latest demo program (neither programs appear to work).
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-06-27 13:25
    I read in the datasheet the MCP3426 address bits are written to the device at the factory. I couldn't find what these bits are set to (though I didn't look very hard).

    I decided to try each possible combination of addresses (I suppose they're not really bits since there are three possible states).

    Here's my latest (failed) demo attempt:
    CON                                
    
      _clkmode = xtal1 + pll16x   
      _xinfreq = 5_000_000
    
    OBJ
    
      Pst : "parallax serial terminal"
      Adc : "jm_mcp342x"
      Format : "StrFmt"
      
    PUB Main | timer, timeoutInterval, address0, address1
                                           
      Pst.Start (115_200)
      
      repeat
        result := Pst.rxcount
        Pst.Str(String(11, 13, "Press any key to start."))
        waitcnt(clkfreq / 10 + cnt)
      until result
    
      Pst.RxFlush
    
      Pst.Clear
    
      Pst.Home
      Pst.Str(String(11, 13, "Initializing ADC Driver"))      
    
      
    
      timeoutInterval := clkfreq * 2
    
      repeat address1 from 0 to 2
        repeat address0 from 0 to 2
          Pst.Str(string(11, 13, "Initializing driver with address =  "))
          Pst.Dec(address1)
          Pst.Str(string(", "))
          Pst.Dec(address0)
          Adc.Start(address1, address0)
          waitcnt(clkfreq + cnt) 
          Pst.Str(String(11, 13, "Set ADC Channel")) 
          Adc.Set_channel(0, Adc#SAMP_16, Adc#GAIN_1)
          timer := cnt
          repeat  
            result := Adc.Ready
          until result or cnt - timer > timeoutInterval
    
          if result
            Pst.Str(string(7, 11, 13, 11, 13, "       SUCCESS!", 11, 13, 7))
            Pst.Str(string(11, 13, "Device found address, A1 =  "))
            Pst.Dec(address1)
            Pst.Str(string(", A0 = "))
            Pst.Dec(address0)
            Pst.Str(string(11, 13, "Starting MainLoop Method"))
            waitcnt(clkfreq * 3 + cnt)
            Pst.Clear
            MainLoop
          else
            Pst.Str(string(11, 13, "Device not found using address  "))
            Pst.Dec(address1)
            Pst.Str(string(", "))
            Pst.Dec(address0)
    
      Pst.Str(string(7, 11, 13, "The sensor was not detected. There is an error either in the "))
      Pst.Str(string(11, 13, "way the sensor is wired or there is an error in the software. "))
      Pst.Str(string(7, 11, 13, "It's also possible the ADC chip is damaged. "))
      Pst.Str(string(11, 13, 11, 13, "End of Program", 7))
      repeat ' keeps cog alive so terminal window doesn't clear
      
    PRI MainLoop | timer, timeoutInterval
    
      timeoutInterval := clkfreq * 2
          
      repeat
         Pst.Home
         Pst.Str(String(11, 13, "Set ADC Channel")) 
         Adc.Set_channel(0, Adc#SAMP_16, Adc#GAIN_1)
         
         Pst.Str(String(11, 13, "Waiting for Ready"))
         timer := cnt
         repeat  
           result := Adc.Ready
         until result or cnt - timer > timeoutInterval
         ifnot result
           Pst.Str(String(7, 13, 13, 13, 13, "Timeout! Check connections.", 7))
         result := Adc.Read_raw
         Pst.Str(string(11, 13, "Channel 1 raw =  "))
         Pst.Dec(result)
         Pst.Str(string(" = "))
         ReadableBin(result, 32)
         '' add readout of processed bits
         
         'waitcnt(clkfreq /20 + cnt)
    
    PUB ReadableBin(localValue, size) | bufferPtr, localBuffer[12]    
    '' This method display binary numbers
    '' in easier to read groups of four
    '' format.
    
      bufferPtr := Format.Ch(@localBuffer, "%")
      
      result := size // 4
       
      if result
        size -= result
        bufferPtr := Format.Bin(bufferPtr, localValue >> size, result)
        if size
          bufferPtr := Format.Ch(bufferPtr, "_")
      size -= 4
     
      repeat while size => 0
        bufferPtr := Format.Bin(bufferPtr, localValue >> size, 4)
        if size
          bufferPtr := Format.Ch(bufferPtr, "_")  
        size -= 4
        
      byte[bufferPtr] := 0  
      Pst.Str(@localBuffer)
    


    Here's the output from the code:
    Initializing ADC Driver
    Initializing driver with address =  0, 0
    Set ADC Channel
    Device not found using address  0, 0
    Initializing driver with address =  0, 1
    Set ADC Channel
    Device not found using address  0, 1
    Initializing driver with address =  0, 2
    Set ADC Channel
    Device not found using address  0, 2
    Initializing driver with address =  1, 0
    Set ADC Channel
    Device not found using address  1, 0
    Initializing driver with address =  1, 1
    Set ADC Channel
    Device not found using address  1, 1
    Initializing driver with address =  1, 2
    Set ADC Channel
    Device not found using address  1, 2
    Initializing driver with address =  2, 0
    Set ADC Channel
    Device not found using address  2, 0
    Initializing driver with address =  2, 1
    Set ADC Channel
    Device not found using address  2, 1
    Initializing driver with address =  2, 2
    Set ADC Channel
    Device not found using address  2, 2
    The sensor was not detected. There is an error either in the
    way the sensor is wired or there is an error in the software.
    It's also possible the ADC chip is damaged.
    
    End of Program
    


    I'm attaching an archive of the program so the child objects are available. The "Format" object is used in the "ReadableBin" method (which I think makes it easier to read binary data).

    I'm not sure what else to try with the software so for now, I'll check my wiring. Hopefully I made an easy to fix wiring error.

    BTW, I'm running the program on a QuickStart board and I'm using the EEPROM's I2C bus.
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-27 14:09
    Here is working code using the adc. driver:
    CON                                 'Belonging to main
            _clkmode = xtal1 + pll16x   'Standard clock mode * crystal frequency = 80 MHz
            _xinfreq = 5_000_000
    
            SDA = 6, SCL = 5             'my wiring               
    VAR
       long  myrawdata          'raw data from adc
       long  millivolts         'raw converted 
       long  symbol            'generally useful
    OBJ
      pst      : "parallax serial terminal"
      adc      : "jm_mcp342x"
    
      
    PUB main                                     
      pst.Start (115_200)                        'start up ser terminal
      pst.str(String("hello, world   "))         'runs in cog 1
      pst.NewLine
      waitcnt(clkfreq/10+cnt)
    
      symbol := adc.startx(SCL, SDA, ADC#L, ADC#L)   'note this is startx, not start
      pst.str(String("Value from adc.startx:   "))   'allows one to spec i2c pins
      pst.hex(symbol,8)
      pst.NewLine
      waitcnt(clkfreq/10+cnt)
     
      repeat
         adc.set_channel(0, adc#SAMP_12, adc#GAIN_1)   'start the conversion
         repeat 
            myrawdata := adc.read_raw   'do a read to get config register
            pst.char("*")
         until adc.ready == TRUE        'until data are valid     
         pst.newline
         pst.str(string("Raw Data: "))
         pst.hex(~~Myrawdata,8)          'sign extended
         myrawdata := ~~myrawdata
         millivolts := adc.raw2mv(myrawdata, adc#SAMP_12, adc#GAIN_1)
         pst.str(string("   Millivolts: "))
         pst.dec(millivolts)      
         pst.newline
         waitcnt(clkfreq+cnt)
    

    Duane, I think you did two things wrong (well, not wrong, just different from what works)
    1. You have to use adc.startx rather than adc.start so that you override the default i2c pins (28 and 29).
    2. After starting the conversion with adc.set_channel, you have to execute a read prior to each time you do adc.ready. adc.ready does NOT actually read the config register.
    3. It is probably easiest to use all the adc#CONSTANTs from the driver to set bits and gain.

    Edit: I take back item 1 since you are using the EEPROM i2c. EndEdit
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-06-27 14:55
    Duane, I think you did two things wrong (well, not wrong, just different from what works)

    I'm glad to know I wasn't doing it wrong. :)

    I was only doing #2 "different from what works." Since I was using the default I2C pins I think the start method shouldn't have been a problem.

    I moved my I2C lines to match yours and sure enough the code works!

    Thank you very much!

    Now I need to add a voltage divider to my circuit since I didn't realize this ADC is saturated at 2.048mV.

    Thank you again. You've saved me from a lot of frustration.

    Edit: The saturation point is 2.048V not 2.048mV.
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-27 15:02
    Wow check out you guys, Tomcrawford hit's the Home Run with the working code. I loaded his code and it worked perfectly in seconds. I appreciate all the hard work from Duane Degn, Tom and everyone else for their input.

    Regards

    Bob
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-27 15:06
    **Thumbs up**
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-06-27 15:15
    DigitalBob wrote: »
    Tomcrawford hit's the Home Run with the working code.

    Tom has a tendency to do that.

    I'm glad you let us know you got your chip working too. I was just wondering if you had seen Tom's code.

    Of course JonnyMac also deserves some praise for contributing the driver. (I learned PASM (and a bunch of other stuff) from reading Jon's Spin Zone columns.)

    What a great forum.
  • DigitalBobDigitalBob Posts: 1,487
    edited 2015-06-27 15:43
    I have not had any time dabble in the PASM code yet
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-27 16:46
    Duane Degn wrote: »

    Now I need to add a voltage divider to my circuit since I didn't realize this ADC is saturated at 2.048mV.

    I have a fixed divider going to one side of the (differential) input and a potentiometer between 3.3 and VSS to the other, so I can test both positive and negative.
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-27 17:30
    Duane Degn wrote: »
    Now I need to add a voltage divider to my circuit since I didn't realize this ADC is saturated at 2.048mV.

    Thank you again. You've saved me from a lot of frustration.

    Edit: The saturation point is 2.048V not 2.048mV.

    I have a fixed divider going to one side of the differential input and a variable divider (pot) going to the other. So I can test positive and negative values.
  • Tracy AllenTracy Allen Posts: 6,656
    edited 2015-06-27 20:08
    About the ±2.048V range. One reason I prefer the similar TI product, the ADS1115, is that the range extends all the way to the power supply rails, even on a 5V supply with the PGAx0 setting. The PGAx1 range is ±4.096V, PGAx2 is ±2.048, and so it, its PGAx16 range is 16 bits over ±256mV, same as the MCP3426. It is really more comparable to the MCP3427, 10-pin msop with address selection. Another nice feature of the ADS1115 is the channels can be configured as a combination of two differential or up to 4 single ended.
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-28 15:27
    I decided, as an exercise, to read the ADC342x with all cases of resolution and gain. Here is the code; it uses the jm driver.
    
    CON                                 'Belonging to main
            _clkmode = xtal1 + pll16x   'Standard clock mode 
            _xinfreq = 5_000_000         '* crystal frequency = 80 MHz 
    
            SDA = 6, SCL = 5             'my wiring               
    VAR
       word  myrawdata          'raw data from adc
       long  millivolts         'raw data converted to millivolts
       byte  rescode            'resolution code: 0..2  for 12-, 14- 16-bit
       byte  gaincode          'gain code:  -..3 for 1, 2, 4, 8                 '
       long  symbol
       
    OBJ
      pst      : "parallax serial terminal"
      adc      : "jm_mcp342x"
      
    PUB main                                     
      pst.Start (115_200)                        'start up ser terminal
      pst.str(String("hello, world   "))         'runs in cog 1
      pst.NewLine
      waitcnt(clkfreq/10+cnt)
    
      symbol := adc.startx(SCL, SDA, ADC#L, ADC#L)   'note this is startx, not start
    
      repeat rescode from adc#SAMP_12 to adc#SAMP_16   'sample size changes slowest
         repeat gaincode from adc#GAIN_1 to adc#GAIN_8    ' 
            adc.set_channel(0, rescode, gaincode)   'start the conversion
            repeat 
               myrawdata := adc.read_raw   'read data and config register
               pst.char("*")
            until adc.ready == TRUE        'until conversion is complete            
            pst.newline
            pst.str(string("Resolution: "))
            pst.dec(lookupz(rescode:12, 14, 16))
            pst.str(string("   Gain: "))
            pst.dec(lookupz(gaincode:1, 2, 4, 8))        
            pst.str(string("   Raw Data: "))
            pst.hex(Myrawdata,4)
            millivolts := adc.raw2mv(myrawdata, rescode, gaincode)
            pst.str(string("   Millivolts: "))
            pst.dec(millivolts)      
            pst.newline
            waitcnt(clkfreq/10+cnt)
         pst.newline      
    
    

    When I first ran this, I observed that the voltage returned varied with the gain variable. Higher gain, lower indicated voltage. It turned out the my voltage dividers were very high impedance (100s of Kohms). I changed the dividers to a few hundred ohms and got much more consistent results. It turns out that the impedance impedance is on the order of 1 Mohm and varies with the gain.
  • It sounds to me like you left off the bypass capacitors from your ADC inputs. There should be a 0.1uF or so (check the datasheet for recommendations) tantalum cap from the center of the voltage divider to ground. This will minimize any voltage depression caused be the ADC acquisition. The differences you are seeing by using different gains are probably caused by the varying ADC input impedances due to the gain differences.

    This will allow you to use your original higher resistor values (verify it with your test program, of course) and greatly reduce your current draw, which is probably why you chose the higher values in the first place.

    I only see small differences reading voltages without a bypass cap on, say, an MCP3304, but when I read small voltages from a shunt, it can be off by a factor of 2 without the caps. Your TI ADC must be rather power hungry.
  • It sounds to me like you left off the bypass capacitors from your ADC inputs.

    Thanks for the reminder. I saw capacitors mentioned in the datasheet but I personally hadn't added them to my circuit. I'll make sure and do this.
    I'm curious to learn how Tom does with caps added to his circuit.
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-06-30 16:41
    The Microchip data sheet doesn't recommend bypass caps on the inputs (actually, it doesn't say anything one way or the other).  It does cover bypass capacitors on VDD (section 6.1.1) and it does cover Input Impedance (section 4.6).  Quote:


    Ideally, the input source impedance should be
    zero. This can be achievable by using an operational
    amplifier with a closed-loop output impedance of tens
    of ohms. 
    I did try a 104 ceramic cap *between* the differential inputs and it didn't really make any difference.  I would worry about frequency response with large Rs and Cs (even knowing the max sample rate is 10s or 100s of Hz already).



  • Nice to see some work being done, instead of the OMG, what is this! :)
  • The interaction of input RC networks with switched capacitor ADC inputs is well understood and is fairly well explained in the data sheets. An even better explanation is given in the data and App notes for ADCs that manage to circumvent the problem. I'm thinking of the Linear Tech "easy drive" series, such as the LTC2487, 16 bits 2/4 channels.
    http://cds.linear.com/docs/en/datasheet/2487ff.pdf
  • When I ran the code with a gain of 1 and feed the ADC with 2.00 volts DC  it read 2.00 volts. But if I  changed it to a gain of 2 the output read 1.00 volt when I used a 2.00 volt input. I figured with a gain of 2 the ADC should read 2.00 volts when I input 1.00 volt. ?????????
  • Did you change the gain parameter in both the adc.set_channel and adc.raw2mv calls?
Sign In or Register to comment.