<< Summary

Read analog data, in Java Raspberry PI

The goal here is to read analog data from a Java program running on a Raspberry PI.
For this example, we will be using a potentiometer like this one.

When the user turns the knob of the potentiometer, its resistance will vary, and the output current will go from 0 Volt (0%) to 3.3 Volts (100%).
We want to catch those numbers from a Java program running on a Raspberry PI.
If we can do that, we can easily replace the potentiometer with a thermometer, a photovoltaic cell, a pressure sensor, and earthquake detector..., whatever can send an analog signal.

The connectors

The Raspberry Pi has a General Purpose Input Output interface (GPIO), the programmable pins can be either on or off, but nothing in between. In (very) short, off means 0 Volt, on means 3.3 Volts.
This is why we need an Analog to Digital Converter (ADC), featured here.
We will connect the GPIO interface of the Raspberry Pi on a Cobbler pinned into a breadboard, next to the ADC.
GPIO Interface The cobbler The ADC
Click to enlarge Click to enlarge Click to enlarge
Slice of PI
Click to enlarge
As you can see, all the pins have different names on each connector... We will need to be pretty carefull with that.

Wiring

Click the diagram below to enlarge it.

Click to enlarge

We give here the connections between the different pins, with their names on each connector. The color is just an indication.
GPIO Cobbler Slice
of PI
MCP3008 Potentiometer Color
Pin # Name
1 3.3 VDC 3V3 3V3
RX
VDD
VREF
right pin, #3 red
6 0V GND GND AGND
DGND
left pin, #1 black
12 GPOI1 #18 G18 CLK orange
16 GPOI4 #23 G23 DOUT yellow
18 GPOI5 #24 G24 DIN blue
22 GPOI6 #25 G25 CS violet
CH0 middle pin, #2 gray

This is what it looks like on the breadboard:

Click to enlarge
The breadboard, the cobbler and the ADC, wired, with the potentiometer.

And here it is, connected to the Raspberry PI:

Click to enlarge
All set with the hardware.

The equivalent, with a slice of PI:

Click to enlarge
More room left on the breadboard.

Now we can begin to worry about the software part.

The Software part

Talk to the MCP3008

The most important here is probably to understand how an ADC works.
Donald Norris gives a good explanation in his book Raspberry Pi Projects for the Evil Genius, but here is a quick summary.
In such a communication - like between the Raspberry Pi and the MCP3008, you need a Master and a Slave.

The Raspberry PI will play the Master.
The MCP3008 will play the Slave.

  1. The fisrt clock pulse (CLK) received by the MCP3008 with its CS pin held low and its DIN pin held high will constitute the start bit.
  2. The SLG/DIFF bit follows next, and the 3 bits that represent the selected channel(s).
  3. After those 5 (start, SLG/DIFF, 3 channel-bits) bits have been received, the MCP3008 will sample the analog voltage during the next clock cycle.
  4. The MCP3008 then outputs what's known as a low null bit, disregarded by the Raspberry PI.
    The following 10 bits - each sent on a clock cycle - are the ADC values. Most Significant Bit (MSB) is sent first, Least Significant Bit (LSB) is sent last.
  5. The Raspberry PI will then put the MCP3008 CS pin high, ending the ADC process.
What we are interested in are those 10 bits mentioned just before (step 4).
On a 10-bit word, it is possible to represent 210 states, which is 1024. This way, 1024 states can be represented, ranging from 0 to 1023.
From the Raspberry PI, you send a clock pulse by turning the CLK pin on and off. This CLK pin is connected to the GPIO1, according to the table and wiring diagram mentioned above.
You will see in the code that we are using MISO and MOSI.
MISO stands for Master In Slave Out.
MOSI stands for Master Out Slave In.

How it translates in Java

Note: For clarity, we showcase here a simple, unique Java class. The code could be much better architected by implementing an Observer and a Listener. Such a code is also available, see in the repository the classes in the adc package, namely adc.ADCObserver, adc.ADCListener and adc.ADCContext. An example showing how to use them is featured in adc.sample.SampleMain.

The heart of this program is the method readAdc(). It uses the pins mentioned earlier, identified with the classes and methods provided by PI4J.
    77    private static int readAdc()
    78    {
    79      chipSelectOutput.high();
    80      
    81      clockOutput.low();
    82      chipSelectOutput.low();
    83    
    84      int adccommand = ADC_CHANNEL;
    85      adccommand |= 0x18; // 0x18: 00011000
    86      adccommand <<= 3;
    87      // Send 5 bits: 8 - 3. 8 input channels on the MCP3008.
    88      for (int i=0; i<5; i++) //
    89      {
    90        if ((adccommand & 0x80) != 0x0) // 0x80 = 0&10000000
    91          mosiOutput.high();
    92        else
    93          mosiOutput.low();
    94        adccommand <<= 1;      
    95        clockOutput.high();
    96        clockOutput.low();      
    97      }
    98  
    99      int adcOut = 0;
   100      for (int i=0; i<12; i++) // Read in one empty bit, one null bit and 10 ADC bits
   101      {
   102        clockOutput.high();
   103        clockOutput.low();      
   104        adcOut <<= 1;
   105  
   106        if (misoInput.isHigh())
   107        {
   108  //      System.out.println("    " + misoInput.getName() + " is high (i:" + i + ")");
   109          // Shift one bit on the adcOut
   110          adcOut |= 0x1;
   111        }
   112        if (DISPLAY_DIGIT)
   113          System.out.println("ADCOUT: 0x" + Integer.toString(adcOut, 16).toUpperCase() + 
   114                                   ", 0&" + Integer.toString(adcOut, 2).toUpperCase());
   115      }
   116      chipSelectOutput.high();
   117  
   118      adcOut >>= 1; // Drop first bit
   119      return adcOut;
   120    }
      
Notice that the steps of the readAdc method are the ones we described in the MCP3008 section.
Bits are read one by one, appropraitely shifted in an int, and returned to the caller.
This readAdc() method is called in a loop from the main method. A SIGTERM (Ctrl+C) will terminate the program.
The value returned by the readAdc method is an int ranging from 0 to 1023, as we said before. This value is converted into a percentage (by dividing it by 10.23, which is 1023 / 100). This value will be sent to the output (displayed) only if it is different from the previous reading.
The loop waits for 100 ms (1/10 s) at its bottom.

The point of truth remains the code checked in the repository.
Here is a version of the full code for the ADCReader class:
      

     1  package analogdigitalconverter;
     2  
     3  import com.pi4j.io.gpio.GpioController;
     4  import com.pi4j.io.gpio.GpioFactory;
     5  import com.pi4j.io.gpio.GpioPinDigitalInput;
     6  import com.pi4j.io.gpio.GpioPinDigitalOutput;
     7  import com.pi4j.io.gpio.Pin;
     8  import com.pi4j.io.gpio.PinState;
     9  import com.pi4j.io.gpio.RaspiPin;
    10  
    11  /**
    12   * Read an Analog to Digital Converter
    13   */
    14  public class ADCReader
    15  {
    16    private final static boolean DISPLAY_DIGIT = false;
    17    private final static boolean DEBUG         = false;
    18    // Note: "Mismatch" 23-24. The wiring says DOUT->#23, DIN->#24
    19    // 23: DOUT on the ADC is IN on the GPIO. ADC:Slave, GPIO:Master
    20    // 24: DIN on the ADC, OUT on the GPIO. Same reason as above.
    21    // SPI: Serial Peripheral Interface
    22    private static Pin spiClk  = RaspiPin.GPIO_01; // Pin #18, clock
    23    private static Pin spiMiso = RaspiPin.GPIO_04; // Pin #23, data in.  MISO: Master In Slave Out
    24    private static Pin spiMosi = RaspiPin.GPIO_05; // Pin #24, data out. MOSI: Master Out Slave In
    25    private static Pin spiCs   = RaspiPin.GPIO_06; // Pin #25, Chip Select
    26    
    27    private static int ADC_CHANNEL = 0; // Between 0 and 7, 8 channels on the MCP3008
    28    
    29    private static GpioPinDigitalInput  misoInput        = null;
    30    private static GpioPinDigitalOutput mosiOutput       = null;
    31    private static GpioPinDigitalOutput clockOutput      = null;
    32    private static GpioPinDigitalOutput chipSelectOutput = null;
    33    
    34    private static boolean go = true;
    35    
    36    public static void main(String[] args)
    37    {
    38      GpioController gpio = GpioFactory.getInstance();
    39      mosiOutput       = gpio.provisionDigitalOutputPin(spiMosi, "MOSI", PinState.LOW);
    40      clockOutput      = gpio.provisionDigitalOutputPin(spiClk,  "CLK",  PinState.LOW);
    41      chipSelectOutput = gpio.provisionDigitalOutputPin(spiCs,   "CS",   PinState.LOW);
    42      
    43      misoInput        = gpio.provisionDigitalInputPin(spiMiso, "MISO");
    44      
    45      Runtime.getRuntime().addShutdownHook(new Thread()
    46                                           {
    47                                             public void run()
    48                                             {
    49                                               System.out.println("Shutting down.");
    50                                               go = false;
    51                                             }
    52                                           });
    53      int lastRead  = 0;
    54      int tolerance = 5;
    55      while (go)
    56      {
    57        boolean trimPotChanged = false;
    58        int adc = readAdc();
    59        int postAdjust = Math.abs(adc - lastRead);
    60        if (postAdjust > tolerance)
    61        {
    62          trimPotChanged = true;
    63          int volume = (int)(adc / 10.23); // [0, 1023] ~ [0x0000, 0x03FF] ~ [0&0, 0&1111111111]
    64          if (DEBUG)
    65            System.out.println("readAdc:" + Integer.toString(adc) + 
    66                                            " (0x" + lpad(Integer.toString(adc, 16).toUpperCase(), "0", 2) + 
    67                                            ", 0&" + lpad(Integer.toString(adc, 2), "0", 8) + ")");        
    68          System.out.println("Volume:" + volume + "%");
    69          lastRead = adc;
    70        }
    71        try { Thread.sleep(100L); } catch (InterruptedException ie) { ie.printStackTrace(); }
    72      }
    73      System.out.println("Bye...");
    74      gpio.shutdown();
    75    }   
    76    
    77    private static int readAdc()
    78    {
    79      chipSelectOutput.high();
    80      
    81      clockOutput.low();
    82      chipSelectOutput.low();
    83    
    84      int adccommand = ADC_CHANNEL;
    85      adccommand |= 0x18; // 0x18: 00011000
    86      adccommand <<= 3;
    87      // Send 5 bits: 8 - 3. 8 input channels on the MCP3008.
    88      for (int i=0; i<5; i++) //
    89      {
    90        if ((adccommand & 0x80) != 0x0) // 0x80 = 0&10000000
    91          mosiOutput.high();
    92        else
    93          mosiOutput.low();
    94        adccommand <<= 1;      
    95        clockOutput.high();
    96        clockOutput.low();      
    97      }
    98  
    99      int adcOut = 0;
   100      for (int i=0; i<12; i++) // Read in one empty bit, one null bit and 10 ADC bits
   101      {
   102        clockOutput.high();
   103        clockOutput.low();      
   104        adcOut <<= 1;
   105  
   106        if (misoInput.isHigh())
   107        {
   108  //      System.out.println("    " + misoInput.getName() + " is high (i:" + i + ")");
   109          // Shift one bit on the adcOut
   110          adcOut |= 0x1;
   111        }
   112        if (DISPLAY_DIGIT)
   113          System.out.println("ADCOUT: 0x" + Integer.toString(adcOut, 16).toUpperCase() + 
   114                                   ", 0&" + Integer.toString(adcOut, 2).toUpperCase());
   115      }
   116      chipSelectOutput.high();
   117  
   118      adcOut >>= 1; // Drop first bit
   119      return adcOut;
   120    }
   121    
   122    private static String lpad(String str, String with, int len)
   123    {
   124      String s = str;
   125      while (s.length() < len)
   126        s = with + s;
   127      return s;
   128    }
   129  }
      
      

Run it

From bash shell, run the following:

 Prompt> CP=./classes:/home/pi/pi4j/pi4j-distribution/target/distro-contents/lib/pi4j-core.jar
 Propmt> sudo java -cp $CP analogdigitalconverter.ADCReader
    
Then turn the knob of the potentiometer, back and forth.
You should see an output like that one:

 Volume:10%
 Volume:11%
 Volume:12%
 Volume:13%
 Volume:14%
 Volume:15%
 Volume:16%
 Volume:17%
 Volume:18%
 Volume:19%
...
    
That is so kewl!

Several channels (MCP3008 channels) can be listened to "simultaneously" (up to 8 on the MCP3008).
I did the test with with several photovoltaic cells, oriented in different directions (like centered, left, right, up, and down). It's quite easy to understand which one gets the most light. Picture that: based on that data, you could very well trigger some servo that would appropriately orient a solar panel, so you re-fuel the batteries of the Raspberry PI. There we go! Now we are self sufficient!

There is in the repository a class named adc.sample.FiveChannelListener, that demonstrates how to listen simultaneously to 5 channels, and render them in "some" graphical way. There is a swing GUI, also checked in, but too demanding on the Raspberry PI.
The one we talk about is using escape sequences, just like 30+ years back... That was yesterday!

Click to enlarge
The setting, with the Slice of PI.

Click to enlarge
The output, through VNC.

Adafruit recommends this setting for the photo resistors:

Click to enlarge
With a 10KΩ resistor

I had actually better results with a 1KΩ resistor.


There is a class, named analogdigitalconverter.mcp3008.MCP3008Reader, easier to reuse.
See an example in analogdigitalconverter.mcp3008.sample.MainMCP3008Sample.


For those interested in comparing Java and C, you have the equivalent C code in the repository as well. This code uses the wiringPi library.


Oliv did it