Using the Raspberry Pi to prototype for Arduino
The
Raspberry Pi has a number of advantages over Arduino boards when it
comes to writing programs. The main one is that it is much easier to
debug a program on a screen than on a headless micro controller. You
can set up an LCD display but they can be quite limited in terms of
what you can show. You can also buy a debuging shield but that adds
another layer of complexity that you might not want to get into.
You
also have a greater choice of IDEs on the Pi. I use Eclipse because
it is available on all platforms that I use. If you are only
programming on the Pi I suggest you find a leaner one that does not
use the Java platform. Java is a bit too resource intensive for the
Pi.
If
you are using an Arduino for simple data gathering then this might be
an overkill but you have 32K of memory (on an UNO) and that allows
you to deploy some non-trivial processing via your sketches.
My
application was a fish tank pH balancer. I bought a temperature probe
and pH stamp from atlas-scientific.com. I wanted to use those in
combination to contiually monitor the pH of our fish tank and when it
drops below a predefined level, use an output pin to release some pH
balancing fluid. The pH stamp uses RS232 serial communication with
the controller and the temperature probe uses an analog input pin.
Using Compiler Directives to Separate Code
The
goal is for the major program logic to be the same for both platforms
and to have the differences minimised, hopefully to the few lines of
code where the platforms need different interfaces. They both can be
programmed in the C language so that was the obvious choice. The
Arduino uses two functions, setup()
and loop()
which can be easily emulated on the Pi (see Example 0). I also used
the wiringpi library because many of the commands in wiringpi are the
same as those on Arduino.
Example
0 - Emulating Arduino on the Pi
int main()
{
setup();
while (1)
{
loop();
}
}
The
basic tool for separating code was the #ifdef
compiler directive. This, along with the #define
directive allows you to controll which code gets compiles, e.g.
Example
1 - Code on the Pi
#define _pi_
#ifdef _pi_
printf("This
will appear on the Pi's standard output");
// this will be
compiled because _pi_ has been defined
#else
lcd.print("Write
to the Arduino's LCD");
// this will be
ignored
#endif
Example
2 - Code on Arduino
//#define _pi_
#ifdef _pi_
printf("This
will appear on the Pi's standard output");
// this will be
ignored
#else
lcd.print("Write
to the Arduino's LCD");
// this will be
compiled because the #define _pi_ has been // commented out
#endif
Caveat 1 - #include is also a compiler directive
The
first problem with this approach showed up when I needed to use
different libraries on the different platforms.
Example
3 - Included Libraries
//#define _pi_
#ifdef _pi_
#include
"wiringpi.h" // will not be ignored by the Arduino compiler
int
someGlobalVariable; // this is code and will be ignored
#else
#include
<LiquidCrystal.h>
// this may be
completely screwed because of the Pi includes above
#endif
The only solution is to delete or comment out the Pi includes when you transfer the code onto the Arduino IDE. This is generally easy because the #include lines are at the top of the file.
Compiler
directives allow you to separate your code but they don't make it any
prettier. You can reduce the number if #ifdefs
by moving them to separate functions. This will make your code more
complex as well so it's a balance between lots of #ifdefs
and function calls.
Beware when moving platforms - int != int
An
issue to remember when programming for two different platforms is
that the fundamentals of the hardware may be different. This is the
case with the Pi and the UNO. The Pi is a 32-bit platform and the UNO
is 16-bit. Therefore an int data type on the UNO has a maximum value
of 32,767. Generally not a problem but my pH stamp needed a baud rate
of 38400bps. This lead to the following, hard to track down problem:
Example
4 - Ints
int baud = 38400;
setup()
{
#ifdef _pi_
serialDevId =
serialOpen(dev, baud);
// will work fine -
baud will equal 38400
#else
Serial.begin(baud);
// will not work at
all - baud will equal -27000 or there abouts
#endif
}
If
you have a true constant that is needed in your program use a #define
instead
Example
4 - Revised
#define baud 38400;
setup()
{
#ifdef _pi_
serialDevId =
serialOpen(dev, baud); // will work fine
#else
Serial.begin(baud);
// will work now -
the compiler will replace baud with 38400
#endif
}
RS232 debugging
The
Pi can also be used for debugging RS232 conversations.
The
pH stamp is designed to have a simple conversation with the micro
controller via RS232. The only feedback that it gave was a green
flash when it got a message it liked and a red flash when it recieved
a bad message. However simple the conversation, it is possible to get
it wrong. For example, I mis-typed 38400 as 34800 and the stamp
refused to respond. Then, I needed to get the termination right - it
needs a return character and not line feed plus return.
The
Pi also has an RS232 interface and with a simple terminal program
like minicom you can see what the UNO is generating and simulate the
conversation with the device.
You
need to set the communications parameters up so that all three
devices are talking the same dialect. The stamp needed 38400bps, no
parity, 8 data bits and one stop bit and so the UNO and Pi have to
comply. Make sure that Tx on the UNO is connected to Rx on the Pi and
simularly Rx on the UNO is connected to Tx on the Pi and you should
be able to happily talk RS232 with your UNO.
Caveat 2 - Arduino UNO has only one RS232 interface
When
using RS232 on the UNO you have to remember that USB uses the same
circuitry as the RS232 pins. Therefore, every time you want to
download a sketch you must disconnect pin 0 (Rx) and pin 1 (Tx). If
you don't do this you'll get a response like "the programmer is
not responding". Frustrating and confusing.
Emulating Analog Input
One
thing you cannot do on the Pi is analog input. To get this working
you are on your own with the UNO. I found that a 16x2 LCD display was
invaluable for this.
If
you need to test the code on the Pi with values representing the
analog input you need to somehow hardwire them into your code. One
way would be like this:
Example
5 - Simulating Analog Input
float analogVals[] =
{12.5, 16.22, 18.43, 22.5};
int currentAnalogVal
= 0;
float
readAnalogVal()
{
#ifdef _pi_
... // check that
currentAnalogVal is not longer than the array
return
analogVals[currentAnalogVal++];
#else
... // get the real
analog value
return
theRealAnalogValue;
#endif
}
Alternatively,
if the analog value is not expected to change (like the temperature
in my fish tank) much you can use a command line, e.g.
Example
6 - Command line input
float
currentTemp; // a global variable
float
readTemperature()
{
#ifdef _pi_
return currentTemp;
#else
// get the real
temperature
#endif
}
#ifdef _pi_ // main
is not needed on Arduino
int main(int argc,
char * argv)
{
currentTemp =
(float)(atof(argv[argc - 1]));
...
}
#endif
Final word - %f is not supported
One
odd little thing that I came across is that the %f string formating
variable is not supported by the Arduino compiler. This might change
at some time in the future but for now,
float floatVar;
char str[] = "
";
sprintf(str,
"%2.2f", floatVar);
will give unexpected results. Instead,
you must use:
float floatVar;
char str[] = "
";
dtostrf((double)
floatVar, 5, 2, str);
Wierd
but true!
I hope this helps - happy programming.
No comments:
Post a Comment