Dr. Arne JachensDr. Arne Jachens

2.1 MCP3208 Analog-Digital-Converter connected via SPI

Raspberry_Inputs

Connection of the MCP3208 AD converter to the RaspberryPi. Colorcoding corresponds to the wires on my board. Since I want to measure warm temperatures, I use resistors of 1.2 kOhm in case of PT1000 sensors and 2.2 kOhm for KTY sensors.

Python interface

"""
Taken and modified from https://pypi.org/project/mcp3208/
to enable chip select for  2 MCP3208
"""
try:
    import  Adafruit_GPIO.SPI as  SPI
except ImportError:
    class SPI(object):
        MSBFIRST = 1
        def  SpiDev(a, b, max_speed_hz): pass
        def  transfer(a): pass
        def  set_mode(a): pass
        def  set_bit_order(a): pass

class MCP3208(object):
    def  __init__(self,chip=0):
        self.spi = SPI.SpiDev(0, chip, max_speed_hz=1000000)
        self.spi.set_mode(0)
        self.spi.set_bit_order(SPI.MSBFIRST)

    def  __del__(self):
        self.spi.close()

    def  read(self, ch):
        if 7 <= ch <= 0:
            raise Exception('MCP3208 channel must be 0-7: ' + str(ch))

        cmd = 128  # 1000 0000
        cmd += 64  # 1100 0000
        cmd += ((ch & 0x07) << 3)
        ret = self.spi.transfer([cmd, 0x0, 0x0])

        # get the 12b out of the return
        val = (ret[0] & 0x01) << 11  # only B11 is here
        val |= ret[1] << 3           # B10:B3
        val |= ret[2] >> 5           # MSB has B2:B0 ... need to move down to LSB

        return (val & 0x0FFF)  # ensure we are only sending 12b

  
class HvcReadSPI:  
    """
    My specific class to connect my analougous sensors.
    """
    def  __init__(self):
        self.ADCPins={}
        self.ADCPins['haus'] =[0,1]
        #self.ADCPins['fire'] =[0,2]
        self.ADCPins['coll'] =[0,3]
        self.ADCPins['flow'] =[0,4]
        self.ADCPins['ret']  =[0,5]
        self.ADCPins['THot'] =[0,6]
        self.ADCPins['TLow'] =[0,7]
        self.ADCPins['ablu'] =[1,2] #1,0
        self.ADCPins['posBp']=[1,1]
        self.ADCPins['zulu'] =[1,0] #1,2
        self.ADCPins['erde'] =[1,3]
        self.ADCPins['folu'] =[1,4]
#        self.ADCPins['posBp']=[1,5]


    def  readADCverbose(self):
        """
        For debugging, this function returns a message with insights into the functionality. 
        """
        ADC={}
        
        adc0 = MCP3208(0)
        adc1 = MCP3208(1)

        message = "HvcReadSPI.readADCverbose \n"
        for  name in self.ADCPins:
            if self.ADCPins[name][0]==0:
                ADC[name] = adc0.read( self.ADCPins[name][1] )
            elif self.ADCPins[name][0]==1:
                ADC[name] = adc1.read( self.ADCPins[name][1] )
            else:
                message = message + "chip not defined! \n"

        message = message + str(ADC) + "\n"
            
        return ADC, message


    def  readADC(self):
        """
        For standard operation, the verbouse message is dropped.
        """
        ADC, message = HvcReadSPI.readADCverbose(self)
        return ADC

"""
for  testing
"""
if __name__ == "__main__":
    mySPI = HvcReadSPI()
    ADC,msg = mySPI.readADCverbose()
    print(msg)
    print("second call")
    ADC = mySPI.readADC()
    print(ADC)

    

 

the following section is from my initial approach, years ago.
You may learn something on how SPI messages are decoded and how a php for wiringPi had been created,
but the section is outdated.

Setup the SPI Bus

The Serial Peripheral Interface (SPI) Bus uses four wires. CS/CE (chip select or chip enable) activates the slave to talk to - the RaspberryPi has two such connections (CE0, CE1). The ship clock (SCLK) gives the pulse for the communication. Each pulse one bit can be sent master out / slave in (MOSI) and at the same time a bit slave out / master in (SOMI) is received. The received bit overrides the sent bit.

In computers it is convenient to group 8 bits as a byte. The necessary sequence to read channel 1 you may get from the documentation MCP3208ADC.pdf:

1. byte
0do not care
0do not care
0do not care
0do not care
0do not care
1start bit
1single mode
0D2
2. byte
0D1
1D0
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
3. byte
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
start bit the change from 0 to 1 starts the communication
Sngl/Diff 1 indicates single mode
D2, D1, D0 is the binary number of the AD channel (0-7)

In my example the resulting bytes look like:

1. byte
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
0do not care
2. byte
1do not care
1do not care
1do not care
0do not care
012. bit
111. bit
010. bit
09. bit
3. byte
18. bit
17. bit
16. bit
15. bit
14. bit
13. bit
02. bit
11. bit
just in case you never had to worry about the bits in your computer before, you have to sort the result as follows:
Shift the 2nd byte 4 bits to the left to drop the first four bits. tmp is stil an unsigned char with 8 bits:
tmp = (byte2<<4)
gives tmp = 0100 0000
Casting the unsigned char to 16 bit integer (the RaspberryPi uses 32 bit, which gives 16 more leading zeros):
result = (int)tmp
gives result = 0000 0000 0100 0000
Shifting the integer again 4 bit to left to have bits at positions 9 - 12:
result = (result<<4)
gives result = 0000 0100 0000 0000
Now add the 3rd byte:
result = result + (int)byte3
gives result = 0000 0100 1111 1101
In decimal world this is 1277, dividing by 212 = 4096 gives 0.31.

My test program I came up with uses Gordons wiringPi library and looks like:

#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <math.h>
#include <wiringPi.h>
#include <wiringPiSPI.h>

double SPI_readMCP3208Channel(chip,channel){
  unsigned char cntrlStack[3];
  unsigned char tmp;
  uint result;
  double realResult;
  /* according figure 6-1 of MCP3208 */
  switch(channel){
  case 0:
    cntrlStack[0]=0b00000110;
    cntrlStack[1]=0b00000000;
    break;
  case 1:
    cntrlStack[0]=0b00000110;
    cntrlStack[1]=0b01000000;
    break;
  case 2:
    cntrlStack[0]=0b00000110;
    cntrlStack[1]=0b10000000;
    break;
  case 3:
    cntrlStack[0]=0b00000110;
    cntrlStack[1]=0b11000000;
    break;
  case 4:
    cntrlStack[0]=0b00000111;
    cntrlStack[1]=0b00000000;
    break;
  case 5:
    cntrlStack[0]=0b00000111;
    cntrlStack[1]=0b01000000;
    break;
  case 6:
    cntrlStack[0]=0b00000111;
    cntrlStack[1]=0b10000000;
    break;
  case 7:
    cntrlStack[0]=0b00000111;
    cntrlStack[1]=0b11000000;
    break;
  }
  cntrlStack[2]=0b00000000;
  /* fprintf(stdout," in1: %X t  in2: %X t  in3 %X n",cntrlStack[0],cntrlStack[1],cntrlStack[1]); */

  wiringPiSPIDataRW (chip,cntrlStack, 3);

  /* fprintf(stdout,"out1: %X t out2: %X t out3 %X n",cntrlStack[0],cntrlStack[1],cntrlStack[1]); */
  tmp=(cntrlStack[1]<<4);
  result=(uint)tmp;
  result=(result<<4);
  result=result+(uint)cntrlStack[2];
  return result;
}

int main (){
  int chip =0;
  int channel=1;
  int result;
  int errno;
  if (wiringPiSPISetup (chip, 2000000) < 0){
    fprintf (stderr, "SPI Setup failed: %s\n", strerror (errno));
  }else{
    printf("SPI okn");
  }
  for (channel=0;channel<8;channel++){
    result = SPI_readMCP3208Channel(chip,channel);
    realResult = (double)result/(double)4096;
    fprintf(stdout,"chn: %i result: %i = %f\n",channel,result,realResult);
  }
  return;
}

First you have to install the "wiringPI" library:
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build

And to use the SPI bus you have to load the module first:
gpio load spi
After update to newest kernel (> 3.18) use sudo raspi-config and in Advanced Options enable the SPI.
After the kernel update all output of the ADC had been zero - until I recompiled the wiringPi.

To compile and execute do:
gcc -I/usr/local/include -c talkSPI.c
gcc -o talkSPI.x talkSPI.o -L/usr/local/lib -lwiringPi
./talkSPI.x

Doing the first tests I got garbage which was due to wrong clock frequency. In the documentation of the MCP3208 there is the note that the chip works with 2 MHz operating at 5 V.
To test I used different combinations of resistors as voltage divider or put the AD channel to 5 V or Ground. This way you can measure the voltage at the input using a multimeter and compare it to the output of the program. (Free inputs which are not connected to some potential will show arbitrary values.)

3.3 PHP Interface

Since I prefer PHP over C and since I plan to do a web frontend for my control, I need PHP interfaces for the wiringPi functions. There are may explanations in the net how to create your own PHP extensions like the "Programming PHP" sec. 14.3 ff of OReilly. To follow these instructions, you have to install the PHP development kit first:
sudo apt-get install php5-dev
Than create your definitions file
cd /home/pi/PHPextension
nano /home/pi/PHPextension/wiringPi.def

int wiringPi_SPI_Setup(int chip, int freq)
string wiringPi_SPI_DataRW(int chip, string command)
int wiringPi_SPI_readMCP3208Channel(int chip, int channel)
void wiringPi_Setup(void)
void wiringPi_pinMode(int pin, string mode)
void wiringPi_digitalWrite(int pin, int state)
void wiringPi_digitalRead(int pin)
and do
/usr/share/php5/ext_skel --extname=wiringPi --proto=wiringPi.def

Enter the generated directory and edit
emacs wiringPi.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_wiringPi.h"
#include <wiringPi.h>
#include <wiringPiSPI.h>

...

/* {{{ wiringPi_module_entry
 */
zend_module_entry wiringPi_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"wiringPi",
	wiringPi_functions,
	PHP_MINIT(wiringPi),
	PHP_MSHUTDOWN(wiringPi),
	NULL,
	NULL,
	PHP_MINFO(wiringPi),
#if ZEND_MODULE_API_NO >= 20010901
	"0.1", /* Replace with version number for your extension */
#endif
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

...

/* {{{ proto int wiringPi_SPI_Setup(int chip, int freq)
    */
PHP_FUNCTION(wiringPi_SPI_Setup)
{
	int argc = ZEND_NUM_ARGS();
	long chip;
	long freq;
	long success;

	if (zend_parse_parameters(argc TSRMLS_CC, "ll", &chip, &freq) == FAILURE) 
		return;

	success = (long)wiringPiSPISetup ((int)chip, (int)freq); 
	RETURN_LONG(success);
}
/* }}} */

/* {{{ proto string wiringPi_SPI_DataRW(int chip, string command)
    */
PHP_FUNCTION(wiringPi_SPI_DataRW)
{
	char *command = NULL;
	int argc = ZEND_NUM_ARGS();
	int command_len;
	long chip;
	/*
	int i;
	char message;
	array_init(message);
	*/
	if (zend_parse_parameters(argc TSRMLS_CC, "ls", &chip, &command, &command_len) == FAILURE) 
		return;

	wiringPiSPIDataRW (chip, command, command_len);
	/*
	for (i=0;i<command_len;i++){
		add_index_string(message, i, command(i));
	} //i
	*/

	//RETURN_STRING(message);
	RETURN_STRING(command, command_len);
}
/* }}} */

/* {{{ proto int wiringPi_SPI_readMCP3208Channel(int chip, int channel)
    */
PHP_FUNCTION(wiringPi_SPI_readMCP3208Channel)
{
	int argc = ZEND_NUM_ARGS();
	long chip;
	long channel;
	int value;
	unsigned char cntrlStack[3];
	unsigned char tmp;

	if (zend_parse_parameters(argc TSRMLS_CC, "ll", &chip, &channel) == FAILURE) 
		return;
	//according figure 6-1 of MCP3208
	switch(channel){
	case 0:
		cntrlStack[0]=0b00000110;
		cntrlStack[1]=0b00000000;
		break;
	case 1:
		cntrlStack[0]=0b00000110;
		cntrlStack[1]=0b01000000;
		break;
	case 2:
		cntrlStack[0]=0b00000110;
		cntrlStack[1]=0b10000000;
		break;
	case 3:
		cntrlStack[0]=0b00000110;
		cntrlStack[1]=0b11000000;
		break;
	case 4:
		cntrlStack[0]=0b00000111;
		cntrlStack[1]=0b00000000;
		break;
	case 5:
		cntrlStack[0]=0b00000111;
		cntrlStack[1]=0b01000000;
		break;
	case 6:
		cntrlStack[0]=0b00000111;
		cntrlStack[1]=0b10000000;
		break;
	case 7:
		cntrlStack[0]=0b00000111;
		cntrlStack[1]=0b11000000;
		break;
	default:
		RETURN_LONG((long)-1);
	}
	cntrlStack[2]=0b00000000;

	wiringPiSPIDataRW (chip, cntrlStack, 3);

	tmp=cntrlStack[1];
	tmp=(cntrlStack[1]<<4);
	value=(long)tmp;
	value=(value<<4);
	value=value+(long)cntrlStack[2];

	RETURN_LONG(value);
}
/* }}} */

/* {{{ proto void wiringPi_Setup()
    */
PHP_FUNCTION(wiringPi_Setup)
{
	if (zend_parse_parameters_none() == FAILURE) {
		return;
	}
	wiringPiSetupSys();
}
/* }}} */

/* {{{ proto void wiringPi_pinMode(int pin, int mode)
    */
PHP_FUNCTION(wiringPi_pinMode)
{
	int argc = ZEND_NUM_ARGS();
	long pin;
	long mode;

	if (zend_parse_parameters(argc TSRMLS_CC, "ll", &pin, &mode) == FAILURE) 
		return;

	pinMode((int)pin, (int)mode);
}
/* }}} */

/* {{{ proto void wiringPi_digitalWrite(int pin, int state)
    */
PHP_FUNCTION(wiringPi_digitalWrite)
{
	int argc = ZEND_NUM_ARGS();
	long pin;
	long state;

	if (zend_parse_parameters(argc TSRMLS_CC, "ll", &pin, &state) == FAILURE) 
		return;

	digitalWrite((int)pin, (int)state);
}
/* }}} */

/* {{{ proto void wiringPi_digitalRead(int pin)
    */
PHP_FUNCTION(wiringPi_digitalRead)
{
	int argc = ZEND_NUM_ARGS();
	long pin;

	if (zend_parse_parameters(argc TSRMLS_CC, "l", &pin) == FAILURE) 
		return;

	digitalRead((int)pin);
}
/* }}} */

Use meld to compare this extract to your generated file.

Now enable the compilation:
emacs config.m4

...
PHP_ARG_ENABLE(wiringPi, whether to enable wiringPi support,
[  --enable-wiringPi           Enable wiringPi support])
...
and run
phpize
and
./configure

If you follow with
make
you will gain garbage since the wiringPi library is not linked - and all experimenting with PHP_ADD_LIBRARY_WITH_PATH and so on gave no success!
Finally I took the stdout of the make command to compile and link manually:

cc -I. -I/home/pi/PHPextension/wiringPi -DPHP_ATOM_INC -I/home/pi/PHPextension/wiringPi/include 
 -I/home/pi/PHPextension/wiringPi/main -I/home/pi/PHPextension/wiringPi -I/usr/include/php5 
 -I/usr/include/php5/main -I/usr/include/php5/TSRM -I/usr/include/php5/Zend -I/usr/include/php5/ext
 -I/usr/include/php5/ext/date/lib -I/usr/local/include 
 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DHAVE_CONFIG_H -g -O2 
 -c /home/pi/PHPextension/wiringPi/wiringPi.c  -fPIC -DPIC -o .libs/wiringPi.o

/home/pi/PHPextension/wiringPi/libtool --mode=link cc -DPHP_ATOM_INC 
 -I/home/pi/PHPextension/wiringPi/include -I/home/pi/PHPextension/wiringPi/main 
 -I/home/pi/PHPextension/wiringPi -I/usr/include/php5 -I/usr/include/php5/main 
 -I/usr/include/php5/TSRM -I/usr/include/php5/Zend -I/usr/include/php5/ext 
 -I/usr/include/php5/ext/date/lib -L/usr/local/lib -lwiringPi
 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -DHAVE_CONFIG_H  -g -O2   -o wiringPi.la -export-dynamic
  -avoid-version -prefer-pic -module -rpath /home/pi/PHPextension/wiringPi/modules  wiringPi.lo 

/home/pi/PHPextension/wiringPi/libtool --mode=install cp ./wiringPi.la /home/pi/PHPextension/wiringPi/modules

To use your new PHP extension you have to edit
sudo nano /etc/php5/{apache2,cli}/php.ini
and add the line

extension=/home/pi/PHPextension/wiringPi/modules/wiringPi.so

To test my work I wrote a small script

<php

/* test SPI, AD converter */
$chip=0;
$freq=2000000;
$success = wiringPi_SPI_Setup($chip,$freq);
echo $success." >0 SPI activated\n";

for ($i=0;$i<8;$i++){
    $channel=$i;
    $value = wiringPi_SPI_readMCP3208Channel($chip, $channel);
    echo $i." value= ".$value." = ";
    $value=$value/4096;
    echo $value."\n";
} #i

/* test GPIO22 switch relay */
wiringPi_Setup();
/* mode 0 = input, mode 1 = output */
wiringPi_pinMode(22,1); 
/* state = 1  on, state = 0 = off */
wiringPi_digitalWrite(22,1);
sleep(2);
wiringPi_digitalWrite(22,0);

?>

and figured out that I had to modify the access rights of the GPIO pins.