c++ SPI register question

Discussion in 'Programming & Software Development' started by jars121, Mar 5, 2018.

  1. jars121

    jars121 Member

    Joined:
    Mar 6, 2008
    Messages:
    1,758
    Location:
    Sydney
    I'm referencing this post to connect to an MCP3208 12-bit ADC. The linked post uses an MCP3008, which is only a 10-bit ADC, so sending/receiving SPI data is slightly different.

    Here is the 10-bit code:

    Code:
    
    mcp3008Spi a2d("/dev/spidev0.0", SPI_MODE_0, 1000000, 8);
    int a2dVal = 0;
    int a2dChannel = 0;
    unsigned char data[3];
    
    data[0] = 1;  //  first byte transmitted -> start bit 
    data[1] = 0b10000000 |( ((a2dChannel & 7) << 4)); // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0) 
    data[2] = 0; // third byte transmitted....don't care
    
    a2d.spiWriteRead(data, sizeof(data) );
    a2dVal = 0;
    a2dVal = (data[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result
    a2dVal |=  (data[2] & 0xff);
    
    cout << "The Result is: " << a2dVal << endl;
    
    
    This code corresponds to SPI timings as follows:

    [​IMG]

    The 12-bit ADC with which I'm interfacing is slightly different (being 12-bit), and has the following SPI timings:

    [​IMG]

    As such the bytes transmitted to the device should be as follows:

    1. Byte 1 = data[0] = 00000110
    2. Byte 2 = data[1] = 00000000
    3. Byte 3 = data[2] = 00000000
    First question, how can I modify the sample code above to incorporate the above 3 bytes? I have no idea what the '|( ((a2dChannel & 7) << 4))' component is for?

    Secondly, the 10-bit device response contains 6 'don't care' bits then bits 9 and 8, followed by bits 7 through 0 in byte 3. How can I modify the adcVal = data[1]... and adcVal |= data[2]... components to reflect this? Again, I have no idea what the '& 0b1100000000' and '0xff' components are for, nor why data[1] is shifted 8 bits?

    Any help would be greatly appreciated.
     
  2. theSeekerr

    theSeekerr Member

    Joined:
    Jan 19, 2010
    Messages:
    2,706
    Location:
    Prospect SA
    OK, one bit at a time...

    Code:
    data[1] = 0b10000000 |( ((a2dChannel & 7) << 4));
    The first bit in the second byte transmitted specifies whether the conversion is single ended or differential - it appears to be labelled as "SGL / (not) DIFF), so a 1 in this position specifies a single-ended conversion. That's what the "0b10000000" is for.

    The next part is the address of the channel. Valid address are from 0-7 ( binary 000 through 111 ), so a2dChannel is logically AND'd with 7 (111) in case a larger number is accidentally requested. The address is followed by 4 "don't care" bits, so the result of the AND is then shifted 4 bits to the left to put the bits in the right place. Finally, this is logically OR'd with the SGL / /DIFF bit above to merge the two bitfields.

    On the output side:
    data[1] contains 6 don't-care bits followed by the 2 most-significant-bits of the result (B8 and B9).
    data[2] contains the 8 least-significant bits of the result (B0-B7).

    Code:
    (data[1]<< 8) 
    takes xxxxxxBB (8 bits, 6 don't cares and 2 data bits) and makes it xxxxxxBB00000000
    (i.e. it constructs 16-bit integer partial result with B8 and B9 in the right place)

    Code:
    data[2] & 0xff
    is another protective logical AND, restricting the possible values from 0-255. It's unnecessary, since data is defined as an array of char, so we can ignore it, and pretend it just says:

    Code:
    a2dVal |=  data[2];
    which is simply a logical OR putting B0-B7 in place in a2dVal.

    Read up on bitwise logical operators and you should be able to work out how to adapt for your 12-bit device. (I could tell you, but where's the fun in that for you?)
     
    Last edited: Mar 6, 2018
  3. OP
    OP
    jars121

    jars121 Member

    Joined:
    Mar 6, 2008
    Messages:
    1,758
    Location:
    Sydney
    Thanks mate, much appreciated :)

    I've since done plenty of reading on bitwise operators, and am starting to feel a little more comfortable. I had some to and fro on another forum on this topic, and believe I've landed on the solution for the 12-bit device, but your explanation above certainly puts it into a context I can comprehend.
     
  4. OP
    OP
    jars121

    jars121 Member

    Joined:
    Mar 6, 2008
    Messages:
    1,758
    Location:
    Sydney
    Follow up query, as I seem to have the ADC working correctly now.

    When a new reading comes in, I replace the existing reading in an array:

    Code:
    a2dVal = (data[1] & 0b0001111) << 8 | data[2];
    someArray.replace(0, a2dVal);
    The above works, but a2dVal is obviously an absolute reading with respect to 4095. I would like the value to be a percentage of the ADC reference. I.e. 100*(a2dVal/4096).

    The next snippet of code doesn't work, yet the following snipped does?

    Code:
    
    a2dVal = (data[1] & 0b0001111) << 8 | data[2];
    someArray.replace(0, 100*(a2dVal/4096));
    //The above results in 0.
    
    
    Code:
    
    a2dVal = (data[1] & 0b0001111) << 8 | data[2];
    someArray.replace(0, a2dVal);
    someArray.replace(0, someArray[0]);
    //This works
    
    What am I missing here? Why can't I apply multiplications/fractions to a2dVal explicitly?
     
  5. dakiller

    dakiller (Oscillating & Impeding)

    Joined:
    Jun 27, 2001
    Messages:
    7,568
    Location:
    Gippsland
    a2dVal/4096 is the issue. a2dVal is going to be an integer and when you divide it by a large number, it cant store the fractional part of the result, so it will cut all of it off. If your number is less than 4096, then the result is always going to be 0.

    Multiply first, then divide, but remember you are only ever going to get integer outputs here.

    (100*a2dVal)/4096
     
  6. theSeekerr

    theSeekerr Member

    Joined:
    Jan 19, 2010
    Messages:
    2,706
    Location:
    Prospect SA
    It looks like he's using a *nix environment, so perhaps an RPi rather than some weedy little 8-bit Arduino. In that case floating point numbers are certainly an option instead.

    Easiest way to do that in C is to add a decimal point to the divisor, like:

    100*(a2dVal/4096.0)

    Obviously in this case you need to store into an array of floats rather than an array of integers.
     
  7. OP
    OP
    jars121

    jars121 Member

    Joined:
    Mar 6, 2008
    Messages:
    1,758
    Location:
    Sydney
    Thanks guys, and my apologies, I should have figured that one out on my own. I'm still fairly new to C, where integer/float/etc. declarations actually matter, as opposed to Python which is my background.

    I'm currently testing on a RPi, but will be moving to Freescale-based SoC shortly :)
     
  8. theSeekerr

    theSeekerr Member

    Joined:
    Jan 19, 2010
    Messages:
    2,706
    Location:
    Prospect SA
    Still, lots of cheap ARM devices have hardware floating point now, so floats may not be an unreasonable choice, depending.
     
  9. OP
    OP
    jars121

    jars121 Member

    Joined:
    Mar 6, 2008
    Messages:
    1,758
    Location:
    Sydney
    I've changed it to a float for the time being, so it's all working nicely now :)
     

Share This Page