The downside of Arduino…
First of all, I really like the Arduino. There are lots of reasons: great community, relatively inexpensive, wide hardware availability and variety, and often a good “impedance” match to projects. But there are a few design choices (both hardware and software) that can be a nuisance, especially as you try to push the limits of what can be done on such inexpensive and low power hardware.
First of all, there is the basic software philosophy. I love the fact that it is a single download: this simplifies installation, and whether you are an expert or a novice doesn’t matter, that’s simply more convenient. But using the program editor provided is simply not that desirable for experts who might be more used to Eclipse, or to an old fashioned kind of guy like myself for whom vi and Makefiles is more reasonable. With a little work you can find out where avr-gcc, avr-libc and avrdude are installed and add them to your search path, but you still have some work to build a Makefile which can compile your sketch from the command line. That’s a little annoying.
But more annoying are the design choices of the libraries themselves.
High among them are the heavy use of delay and the relative invisibility of interrupts. This may simplify short programs, but it provides no help to more sophisticated programs, and may in fact hinder them. Consider my experiments of last night (which fueled the rant of this morning). I merely wanted to implement a serial terminal using the TVout library and some serial communications library. The idea would be “read from serial, print to TVout”, voila, should be simple.
TVout generates NTSC video on the fly, and it basically works great. To do so, it uses regular timer interrupts. and if you dig into the library itself, you might find out that it uses Timer1. At regular interval, the timer 1 counter overflows, and the library knows that it’s time to bitbang out the video using some carefully constructed code. It also appears to use Timer2 to generate audio tones. Hmmm. The AVR chip that underlies the normal Arduino has only three timers… is that going to be a problem? How can I tell if another library also uses those timers?
You won’t find it in the documentation to any library. Arduino programming is supposed to be easy, and remove the need to understand how the underlying hardware works. Except that when you try to use two libraries together, and they happen to both use the same underlying timer resources, it doesn’t work right. There are no compile issues, it’s just one or both of the modules fail.
Which it did of course last night. You’d think that a loop like:
[sourcecode lang=”cpp”]
void
loop()
{
TV.print((char)Serial.read()) ;
}
[/sourcecode]
might have a chance of working, but it doesn’t. It drops and mangles characters horribly. While you might be able to poll some switches, apparently typing is something that is simply beyond the realm of expectation.
Some of you are probably going to leap to TVout’s defense, claiming that “You’re not doing it right! There is an example sketch which shows you the right way to do Serial and TVout!” Let’s have a look at that little sketch, shall we?
[sourcecode lang=”cpp”]
#include <TVout.h>
#include <pollserial.h>
#include <fontALL.h>
TVout TV;
pollserial pserial;
void setup() {
TV.begin(_NTSC,184,72);
TV.select_font(font6x8);
TV.println("Serial Terminal");
TV.println("– Version 0.1 –");
TV.set_hbi_hook(pserial.begin(57600));
}
void loop() {
if (pserial.available()) {
TV.print((char)pserial.read());
}
}
[/sourcecode]
Okay, we have some new library, which we never heard of before. It’s apparently part of the TVout download. Documentation? No. It calls a function I haven’t seen before, “set_hbi_hook”. Is that documented? No. I am pretty sure it’s a hook which will get called at the end (or the beginning?) of a horizontal blanking interrupt (this isn’t my first rodeo) but what’s really going on here. The pserial.begin call must return a function… time to crack open the source code.
And, it’s about what you expect. There is a hook function that gets called on every line, right before the line is rendered. My guess is that it’s bad if the timing of the hook function is in anyway indeterminate, because then the rows will start at odd intervals. But each render routine starts with a line which waits… for some length…. maybe there is some amount of time (undocumented, different for PAL/NTSC, maybe just the front part of each scanline, read more code) that if you don’t exceed, you’ll be fine. What does pollserial do? Well, it snoops at the serial data registers (polling!) to see if a new character has arrived. It then puts it in a ringbuffer, so that it can be made available to the read call later. Okay, I understand.
But did I mention the reason I didn’t use this code in the first place? It’s that pollserial didn’t compile on my Arduino 1.0 setup (most libraries that aren’t part of the core don’t out of the box yet, in my experience). I could probably figure that out (has something to do with inheriting from the Print class and a prototype mismatch) but in my real, ultimate application, I wanted to read from a PS/2 keyboard. It will of course have the same (but differing in details) issues, and I’ll have to tweak their driver code to make it work with TVout too. Sigh.
Ultimately, I guess I disagree with one part of the Arduino philosophy: that programming can ever really be simple. Writing a program to blink an led, or read a switch isn’t hard, no matter what language or processor you use. Simple programs are simple, but the virtue of computers is that they can do things which are complex. If your software environment doesn’t give you sufficient help in organizing and doing complex actions, then you are missing out a great deal of the purpose of computers.
Addendum: While trying to fix the compile errors in pollserial, I found this fragment:
[sourcecode lang=”cpp”]
int pollserial::read() {
if (rxbuffer.head == rxbuffer.tail)
return -1;
else {
uint8_t c = rxbuffer.buffer[rxbuffer.tail];
//tail = (tail + 1) & 63;
if (rxbuffer.tail == BUFFER_SIZE)
rxbuffer.tail = 0;
else
rxbuffer.tail++;
return c;
}
}
[/sourcecode]
Can you spot the problem? Hints: BUFFER_SIZE is defined to 64, and rxbuffer.buffer is malloced to be BUFFER_SIZE bytes long.