Saturday, December 22, 2012

Analog Interface

The capability to read analog inputs is a feature that is greatly missed on the Raspberry Pi, but I agree with the decision to omit this capability in order to keep the price down.  Besides, if they did include an analog interface, many would complain that it isn't adequate for their purpose.  How many input channels do you need?  What resolution? 8 bit, 10 bit, 12 bit 16 bit?  What throughput rate?

Fortunately, there are many analog input chips that use the SPI or I2C bus, making it almost trivial to add analog inputs to the Pi.  I chose the MCP3008, an 8 channel 10 bit ADC available from Adafruit.com.  Add a bi-directional logic level converter and some connectors and you're ready to go.

The level converter is on my main interface board which provides two SPI bus connectors.  The analog interface is a simple board which includes a connector for SPI, the MCP3008 chip, a jumper to choose the analog reference, and screw terminals for the inputs.  I ended up adding several more screw terminals connected to 5V in order to power thermistors. 

The SPI serial bus is full duplex, but the way it works may seems odd to a programmers point of view.  (It makes perfect sense if you understand how the hardware works.)  You may be familiar with how full duplex works on an RS-232 line: data can be sent and received at the same time, but independently.  That independence is due to the fact that RS-232 is an asynchronous protocol.  SPI is a synchronous protocol; meaning everything is driven by a clock pulse.
Data bits will be sent out the MISO line on each cycle of the CLOCK line.  At the same time data bits are being read in on the MOSI line.  The number of bits out is the number of bits you will read back in.  This means that you may have to write more bits than expected for a given command and you may read bits that are unused.

Fortunately, we don't have to worry much about the ugly details at the lower level.  There is a device driver for SPI that is included with the recent versions of Raspbian and the WiringPi library provides support for SPI I/O.  The functions wiringPiSPISetup and  wiringPiSPIDataRW are all that is needed.  Here is the source code for a program that I used when testing and calibrating sensors.


#include <stdio.h> #include <stdint.h> #include <wiringPi.h> #include <gertboard.h> // read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) int readadc(adcnum) { uint8_t buff[3]; int adc; if ((adcnum > 7) || (adcnum < 0)) return -1; buff[0] = 1; buff[1] = (8+adcnum)<<4; buff[2] = 0; wiringPiSPIDataRW(0, buff, 3); adc = ((buff[1]&3) << 8) + buff[2]; return adc; } int main(int argc, char *argv[]) { int i, chan; uint32_t x1, tot ; printf ("SPI test program\n") ; // initialize the WiringPi API if (wiringPiSPISetup (0, 1000000) < 0) return -1 ; // get the channel to read, default to 0 if (argc>1) chan = atoi(argv[1]); else chan = 0; // run until killed with Ctrl-C while (1) { tot = 0; for (i=0; i<100; i++) { // read data and add to total x1 = readadc(chan); tot += x1; delay(10); } // display the average value printf("chan %d: %d \n", chan, (tot/100)) ; } return 0 ; }

Note: The SPI device is not loaded by default.  The easy way to get it loaded is to used the "gpio" utility that comes with the WiringPi library.  Just enter
      gpio load spi
and the drivers will be loaded and ready to use.

Tuesday, December 4, 2012

Dual power supplies is the way to go

You may notice in the picture of my setup (in the previous post) that the Raspberry Pi is being powered normally via the USB power connector.  This was done because the Pi was failing to boot when powered through the GPIO port as I had planned.  While I did not do any tests, the obvious cause is a lack of sufficient current to power the Pi.  It really does want to have a solid 700mA of 5 volt juice.  My planned configuration worked fine on the workbench but failed once I mounted it on the wall and connected the alarm sensors.  The sensors require 12V power and I was using the same power supply to drive the sensors and the power adapter for the interface board.  That 12V power supply was only 2A and that was apparently too little for all of this.

I could have gotten a larger 12V power supply, but I already have several of these 2A supplies. Realization finally struck me that using a single power supply would be a mistake.  If any of the sensor lines was compromised, shorting the power to ground, it would cause the Pi to shut down suddenly.  So now I have one supply just for the alarm sensors and another that drives the interface board, and through that, the Pi.

The system is up and has been running for over a week with no major problems.  I spent several hours improving my code for driving the X10 interface to make it as reliable as possible.  It seems to work as well as it ever has when using heyu or ActiveHome to drive it.  X10 is inherently unreliable, but is usually good enough for casual use.  If you have a fairly new house that is wired properly, then it can work fairly well.  My house, unfortunately, is old and poorly wired.  I have made some improvements over the years, but there are still areas of my house that the X10 signals simply will not reach.  I will cover X10 in more detail in an future post.

Tuesday, November 27, 2012

Mounted and Testing Begun

The interface is complete now with all (well, most anyway) of the kinks out of it.  Here it is mounted on the wall in a utility area of the house.


I still need to install a few more sensors for the alarm system and I will soon add analog input using the SPI bus.  That worked just as expected on the breadboard.  You can see it connected in the blog post below.

There was some discussion on the forum about the serial connection for the X10 interface.  My CM11a works fine with just Tx, Rx, and GND connected.

The power supply was salvaged from a micro ATX PC case.  The 12V power supply that powers it is only 2A and I had trouble running the RasPi powered from my interface.  It worked fine on the bench with these same power supplies.  I assume that the motion detectors, which are wired to the same 12V supply are drawing too much current. This causes the ATX PS to fail to output adequate 5V current.  For the time being I have removed the power jumper (+1 for configurability) and just power it the normal way.  Once I get a beefier 12V PS, I will try powering the RasPi from the interface again.

Monday, November 5, 2012

Interface Completed

After yet another design change, I finally have the interface complete and ready to test.
Version 7 Finally Comes to Life



          ✓ Serial Port
          ✓ 8 Digital Inputs
          ✓ 4 Relay Outputs
          ✓ 2 SPI Ports
          ✓ 1 I2C Port
          ✓ Fused Power to Pi




Making the fuse involved the tiniest soldering I have ever done.  That is a surface mount poly-fuse soldered to two wires.  Once connectors are added, it is used for the jumper that connects the interface board 5V supply with the Raspberry Pi GPIO 5V pin.

The picture above shows one SPI port connected to an ADC chip on the bread board.  Once everything checks out, it will be time to mount it all.

Monday, October 15, 2012

Interface Nearly Complete

After far too many revisions, the GPIO interface for the Raspberry Pi alarm system is nearly done.  It has:

• 8   Digital Inputs
• 4   Relays
• 1   RS-232 serial port
• 2   SPI bus connectors
• 1   I2C bus connector (eventually)

I still need to to finish wiring the SPI connectors, but the serial port, the digital inputs, and the relays are all working.  I will probably go ahead and add connectors for the I2C bus pins for future use.  I already have an eight port A/D converter for the SPI bus and more GPIO would also be easy to add via SPI.

I added a jumper block near the GPIO connector that allows me to connect (or not) 5V on the interface to 5V on the RasPi.  This would allow me to power the board from the RasPi or to backfeed power to the RasPi from the board.  I plan on making a jumper with a polyfuse inline.  Using that as the jumper will prevent the  RasPi from drawing too much current.

Monday, October 8, 2012

Source Code - Installment One

Edit 15-Oct-2012:   Posted corrected code - missed a few typos. GpioPoller.c is now the multiplexed version. Edit to make HTML behave nicely.

I have been looking at this source code issue from the wrong perspective.  I knew that I would be posting my source code here eventually, but I didn't think that it would be useful to that many people.  That was when tunnel vision had me thinking of just this alarm system project.

The example code that I present here is really much more widely applicable.  This is my main function, which implements a daemon process in C.  Also included are my data structures and my method for using worker threads.  Copious comments have been added to help clarify things.

RPiHouse.h    data structures, function prototypes, and global variables

/*--------------------------------------------------------------------------- RPiHouse.h - include file for the Raspberry Pi re-write of controld 08-Aug-2012 Ted Hale add enums and new dev struct 08-Oct-2012 Cleanup and comments for release of source ---------------------------------------------------------------------------*/ #define PIDFILE "/var/run/RPiHouse.pid" #define CONFIGFILE "/pihome/RPiHouse.conf" #define DEVICEFILE "/pihome/devices.conf" #define MAXDEVICES 100 #define MYPORT 17100 #define MAXCONNECTIONS 10 #define BYTE unsigned char //#include "mysql.h" //#include "mysqld_error.h" // causes Global variables to be defined in the main // and referenced as extern in all the other source files #ifndef EXTERN #define EXTERN extern #endif // device types typedef enum { X10, Gout, Gin } DevType; // device categories typedef enum { Light, OutdoorLight, MotionSensor, DoorSensor, Other } DevCategory; // the device structure typedef struct { char *name; DevType type; BYTE addr; BYTE house; DevCategory category; char *oncmd; char *offcmd; int stat; time_t tOn; time_t tOff; } Device; // "at" commands structure. this is used in a linked list typedef struct { char *cmd; // command to perform time_t time; // when to perform it int period; // -1: one time, else seconds to add for next time void *next; // next in queue } At_qEntry; // prototype definitions for the worker threads void *ListenerThread(void *param); void *GpioPoller(void *param); void *X10Thread(void *param); void *LogicThread(void *param); // some other prototype definitions int DoCommands(char *Cmd); int LogToClients(char *format, ... ); int AtCmd(char *cmd); void X10TurnOn(char *dev); void X10TurnOff(char *dev); void DoTurnOnOff(char *name, int onoff); char *CatName(int n); char *TypeName(int n); // GLOBAL variables. A lock needs to be used to prevent any // simultaneous access from multiple threads EXTERN int kicked; // flag for shutdown or restart EXTERN int nDevices; // number of devices defined EXTERN Device dev[MAXDEVICES]; // the array of devices ///EXTERN MYSQL *conn; // the DB connection EXTERN int logsock[MAXCONNECTIONS]; // log listeners EXTERN time_t Sunrise; // time of sunrise for today EXTERN time_t Sunset; // time of sunset for today

main.c    The entry point for the program

/*--------------------------------------------------------------------------- main.c By Ted B. Hale part of the home alarm and automation system previously known as "control" renamed to RPiHouse for this rebuild on the Raspberry Pi This file implements the main for a daemon process 01-Nov-2009 Starting over from scratch (mostly) on Linux 23-Jul-2010 on VersaLogic Jaguar embedded system now disabled weather thread 03-Aug-2012 re-write for Raspberry Pi 08-Oct-2012 Cleanup and comments for release of source ---------------------------------------------------------------------------*/ #include <errno.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <sys/timeb.h> #include <pthread.h> // database not used yet //#define dbhost "localhost" //#define dbuser "control" //#define dbpass "secret" //#define dbdatabase "control" // this defines the pre-processor variable EXTERN to be nothing // it results in the variables in RPiHouse.h being defined only here #define EXTERN #include "RPiHouse.h" //************************************************************************ // reads the device file defined in RpiHouse.h andsets up the dev table int ReadDevices() { FILE *f; char line[200]; char *p; int i, n; // open the device config file f = fopen(DEVICEFILE,"r"); if (!f) { Log("Failed to open device file [%s]\n",CONFIGFILE); return 0; } // read lines from the file. It is structured like a windows ini file // where the device names are the section names ( enclosed in [] ) nDevices = 0; n = -1; while (read_line(f,line)>=0) { //Log("READCONFIG: %s",line); // these are all lines to ignore - comments and lines that are blank if ((line[0]==';')||(line[0]=='#')||(line[0]==' ')||(line[0]==0)) continue; // is this a device name if (line[0]=='[') { n++; nDevices = n+1; // is the table full if (nDevices>=MAXDEVICES) { Log(" ***** Out of devices *****"); break; } // add a new device to the table dev[n].name = strdup(line+1); p = strchr(dev[n].name,']'); if (p) *p = 0; dev[n].stat = 0; dev[n].tOn = 0; dev[n].tOff = 0; continue; } // ignore everything until a device is defined if (nDevices==0) continue; // parse out "variable=value" // get pointer to = p = strchr(line,'='); // if no =, then skip if (!p) continue; // this will put a null terminator after the variable name *p=0; // bump p by 1 to have it point at the value p++; // the line may have a newline or other character on the end // this will fix that if (p[strlen(p)-1]<' ') p[strlen(p)-1] = 0; // set specified variable // device type if (!strcmp(line,"type")) { if (!strcasecmp (p,"X10")) { dev[n].type = X10; } else if (!strcasecmp (p,"Gout")) { dev[n].type = Gout; } else if (!strcasecmp (p,"Gin")) { dev[n].type = Gin; } } // device address if (!strcmp(line,"addr")) { dev[n].addr = atoi(p); } // house code part of address for X10 devices if (!strcmp(line,"house")) { dev[n].house = *p-'A'; } // device category if (!strcmp(line,"category")) { if (!strcasecmp (p,"Light")) { dev[n].category = Light; } else if (!strcasecmp (p,"MotionSensor")) { dev[n].category = MotionSensor; } else if (!strcasecmp (p,"DoorSensor")) { dev[n].category = DoorSensor; } else { dev[n].category = Other; } } // execute this command when the device turns on // mostly useful for GPIO inputs but can be used for X10 too if (!strcmp(line,"on")) { dev[n].oncmd = strdup(p); } // same for when it turns off if (!strcmp(line,"off")) { dev[n].offcmd = strdup(p); } } ///Log("Done reading devices"); // close the config file fclose(f); // output a table of the devices to the log file for (i=0; i<nDevices; i++) { Log(" %-20s %-15s %-5s address: %2d %2d", dev[i].name, CatName(dev[i].category), TypeName(dev[i].type), dev[i].addr, dev[i].house); } return 0; } //************************************************************************ // handles signals to restart or shutdown void sig_handler(int signo) { switch (signo) { case SIGPWR: break; case SIGHUP: // do a restart Log("SIG restart\n"); LogToClients("SIG restart"); kicked = 1; break; case SIGINT: case SIGTERM: // do a clean exit Log("SIG exit\n"); LogToClients("SIG exit"); kicked = 2; break; } } //************************************************************************ // and finally, the main program // a cmd line parameter of "f" will cause it to run in the foreground // instead of as a daemon int main(int argc, char *argv[]) { pid_t pid; FILE *f; pthread_t tid1,tid2,tid3,tid4,tid5; // thread IDs struct tm *today; // check cmd line param if ((argc==1) || strncmp(argv[1],"f",1)) { //printf("going to daemon mode\n"); // Spawn off a child, then kill the parent. // child will then have no controlling terminals, // and will become adopted by the init proccess. if ((pid = fork()) < 0) { perror("Error forking process "); exit (-1); } else if (pid != 0) { exit (0); // parent process goes bye bye } // The child process continues from here setsid(); // Become session leader; } // trap some signals signal(SIGTERM, sig_handler); signal(SIGINT, sig_handler); signal(SIGPWR, sig_handler); signal(SIGHUP, sig_handler); // save the pid in a file pid = getpid(); f = fopen(PIDFILE,"w"); if (f) { fprintf(f,"%d",pid); fclose(f); } // open the debug log LogOpen("/pihome/logs/RPiHouse"); // database not added back yet // init MySQL interface /* conn = mysql_init(NULL); if (conn == NULL) { Log("mysql_init Error %u: %s\n", mysql_errno(conn), mysql_error(conn)); } else { if (mysql_real_connect(conn, dbhost, dbuser, dbpass, dbdatabase, 0, NULL, 0) == NULL) { Log("Error %u: %s\n", mysql_errno(conn), mysql_error(conn)); mysql_close(conn); conn = NULL; } }*/ // start the main loop do { LogToClients("STARTING"); // read config info ReadDevices(); // start the various threads tid1 = tid2 = tid3 = tid4 = tid5 = 0; pthread_create(&tid1, NULL, GpioPoller, NULL); pthread_create(&tid2, NULL, X10Thread, NULL); pthread_create(&tid3, NULL, LogicThread, NULL); pthread_create(&tid4, NULL, ListenerThread, NULL); ///pthread_create(&tid5, NULL, MiscThread, NULL); // wait for signal to restart or exit do { sleep(1); } while (!kicked); // wait for running threads to stop if (tid1!=0) pthread_join(tid1, NULL); if (tid2!=0) pthread_join(tid2, NULL); if (tid3!=0) pthread_join(tid3, NULL); if (tid4!=0) pthread_join(tid4, NULL); if (tid5!=0) pthread_join(tid5, NULL); // exit? if (kicked==2) break; // else restart, set flag back to 0 kicked = 0; } while (1); // forever // delete the PID file unlink(PIDFILE); return 0; }


GpioPoller.c    The GPIO polling thread

/*--------------------------------------------------------------------------- GpioPoller.c Poll the GPIO devices via the wiringPi interface Ted Hale 08-Aug-2012 initial version for Raspberry Pi re-write of controld 08-Oct-2012 Cleanup and comments for release of source 14-Oct-2012 modify for muxed input ---------------------------------------------------------------------------*/ #include <errno.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <sys/timeb.h> #include <pthread.h> #include <wiringPi.h> #include "RPiHouse.h" // Thread entry point, param is not used void *GpioPoller(void *param) { int pin, i, x, a0, a1, a2; // initialize the WireingPi interface Log("GpioPoller: init wiringPi"); if (wiringPiSetup () == -1) { Log("Error on wiringPiSetup. GpioPoller thread quitting."); return; } Log("GpioPoller: init devices"); // initialize input circuit // muxed input - 0 is input 1-3 are address bits pinMode (0, INPUT); pullUpDnControl(0,PUD_UP); for (i=1; i<4; i++) { pinMode (i, OUTPUT); digitalWrite(i, 0); } // relay outputs are 4-7 for (i=4; i<8; i++) { pinMode (i, OUTPUT); digitalWrite(i, 0); } // start polling loop do { for (i = 0 ; i < nDevices ; i++) { switch (dev[i].type) { case Gin: // set mux address a0 = ((dev[i].addr & 1)==1)?1:0; a1 = ((dev[i].addr & 2)==2)?1:0; a2 = ((dev[i].addr & 4)==4)?1:0; digitalWrite(1, a0); digitalWrite(2, a1); digitalWrite(3, a2); // mux needs a tiny amount of time for the value to settle Sleep(0); // input is pulled high, so 1 is off and 0 (shorted to ground) is on x = digitalRead(0); // if on if (x==0) { // and it wasn't already on if (!dev[i].stat) { Log("GpioPoller> %s ON\n",dev[i].name); LogToClients("%s ON",dev[i].name); /////DoCommands(dev[i].oncmd); } time(&dev[i].tOn); dev[i].stat=1; } else { // if Off // and it wasn't already off if (dev[i].stat) { Log("micropoller> %s OFF\n",dev[i].name); LogToClients("%s OFF",dev[i].name); /////DoCommands(dev[i].offcmd); } time(&dev[i].tOff); dev[i].stat=0; } // Save to DB // ??? this should be only when the value changes !!! /*if (conn!=NULL) { sprintf(sql,"insert into data (var,val) VALUES ('%s',%d)",dev[i].name,dev[i].stat); if (mysql_query(conn, sql)) { Log("mysql_query Error sql: %s\n errno = %u: %s", sql, mysql_errno(conn), mysql_error(conn)); } }*/ break; case Gout: // do nothing break; default: // other modes not yet supported break; } } // let other thread run, sleep 10ms Sleep(10); } while (kicked==0); // exit loop if flag set }

Folder Paper Case

I saw a post on the RasPi forum today about a new folder paper case and I thought I would try it out since my RasPi number 4 needs a home.

http://www.iammer.com/raspi/case.html

I printed it on heavy card stock and on regular paper.  The regular paper was used for practice.  Good idea since I found that I folded it upside down.  Fold it with the printed side down.  I used scissors, an exacto knife and an old piece of wood as a cutting board.  Here is how it went.

All it takes - printed card stock, scissors, and a knife.

After being cut out.

Make good creases on all the folds.

The result - Very functional and surprisingly study little box.

The connectors and SD card hold the RasPi tightly in place.
I like the result.  Especially for a case that is basically free (assuming you can get a sheet of heavy card stock.)  The plain white looks really boring, but it should be pretty easy to add whatever design you like.


Flip the case over and there is lots of space to express your inner Pi.  OK, so I'm not Picasso.