Serial Demo in C for the Arduino Uno
Now we are going to see where the rubber meets the road.
The Uno (using the atmega328P chip) has a hardware serial port.
The question is whether we need to reinvent the wheel and write a serial
driver, or if there is existing code that we can leverage.
What about grabbing something from Arduino 1.0.1 ?
The thing we might want to grab is /usr/share/arduino/hardware/arduino/cores/arduino/HardwareSerial.cpp
(along with HardwareSerial.h most likely). This involves us in calling C++ objects from C code, or with writing
our own code in C++. I am not sure how difficult the former is (or if it is possible at all).
Coding in C++ is one of the very things I am striving to avoid by not using the Arduino GUI.
So it is on to other options.
What about wiring_serial.c ?
Back in the old days of arduino, there was wiring_serial.c instead of HardwareSerial.cpp.
This was present in arduino-0015 (March of 2009), being replaced by HardwareSerial.cpp in
arduino-0016 (May of 2009). Happily, old versions of the arduino distribution can still
be obtained. The question is how suitable this will be for a newer board like the Uno.
And it turns out to be just fine (see below), which is not all that surprising given
that the Uno is based on the venerable atmega328 chip).
What about the Procyon avrlib ?
The Procyon avrlib was last updated in September of 2005.
It has a file called uart.c (along with uart.h) that could serve our purposes.
I am finding though that it will take some work to get it to compile with the
current avr-gcc (in October of 2012 using avr-gcc version 4.6.3).
I am also concerned about multitudes of dependencies with other parts of avrlib.
Dive in with wiring_serial.c
Wiring_serial.c turns out to be not too entangled with other things. I only need 3 header files
wiring_private.h wiring.h and binary.h. I actually don't need binary.h but it gets pulled
in by the other header files. I use the files from arduino-0015, and ignoring binary.h this
is only about 300 lines of code. The best part of all is that it works right away at 9600 baud.
Can we go faster? I am downloading to the Uno at 115200 baud.
It turns out that I can use 115200 baud just fine with absolutely no hassles.
Linux accepts B115200 as a defined speed for /dev/ttyACM0 (somewhat to my surprise).
"ACM" by the way is the USB "Abstract Control Module" in linux and uses the cdc_acm driver,
which may be of importance someday. The letters "CDC" stand for Communications Device Class".
A peek at the driver source cdc_acm.c shows the following list of speeds,
which bodes well for the future with things like the ATmega32u4:
static const __u32 acm_tty_speed[] = {
0, 50, 75, 110, 134, 150, 200, 300, 600,
1200, 1800, 2400, 4800, 9600, 19200, 38400,
57600, 115200, 230400, 460800, 500000, 576000,
921600, 1000000, 1152000, 1500000, 2000000,
2500000, 3000000, 3500000, 4000000 };
The interface to wiring_serial is very straightforward:
- beginSerial (baud); initialize and specify baud rate.
- serialWrite (ch); transmit a byte.
- serialAvailable (); boolean to check if we can read.
- serialRead(); read a byte.
And wiring_serial is interrupt driven (for receive), who could ask for more?
What about printf() ?
Or in general, what about the whole gamut of C library routines
that we know and love? Given serialWrite() (which is the moral
equivalent of putchar() or putc(), it is trivial to write a puts()
routine, which is immensely handy. Given that and sprintf() --
which is available in avr-libc, it is straightforward to cobble
together a printf routine. But it turns out it is even easier than
that. This is hardly an intended consequence, I am sure, but it
turns out that if you provide a puts() routine, you can just use
the printf() routine in avr-libc (no doubt it is calling puts() once
it has the string formatted, and it just works ... for now.
There are more official ways to do things using avr-libc.
In fact there is a uart module in there, and ways to hook up the
uart routines to stdout so that the whole gang of C library routines
will be reading and writing from the serial port. There is a swell
demo that gives all the details of this in the avr-libc online documentation.
My only concern is how much code bloat will result. Perhaps the best
approach would be just to use the routines in avr-libc and not worry
about things until code size becomes an issue.
The clever folks who have put together avr-libc are not heartless with respect to
the issues of code size. You should look at the documentation for
vfprintf, which is at the heart of much of this. It can be obtained
in 3 flavors by using link time switches. By default you get a version
that leaves out floating point formatting (if you want that, use the
link switches: "-Wl,-u,vfprintf -lprintf_flt -lm"). The third option
is a minimal version that handles only integer and string formatting,
and you obtain it by using the link switches: "-Wl,-u,vfprintf -lprintf_min".
This is hardly the place for it, but once I got printf() going,
I was curious to find out the size of various integers under avr-gcc:
- short - 2 bytes
- int - 2 bytes
- long - 4 bytes
- long long - 8 bytes
Testing with picocom
This turns out to be the best thing since sliced bread.
I have wrestled with minicom under linux before, and this is far better.
yum install picocom
picocom -b 115200 /dev/ttyACM0
And voila! I have a terminal window. No insane nonsense as with minicom
which wants to try to send "AT" commands to a supposed modem by default
(which might have made sense back around 1975 or so, ... maybe).
To get out of picocom type the two character salute ^A^X.
And this is a fantastic feature: you can go up and down the set
of known baud rates using ^A^U to go up and ^A^D to go down.
Pure genius! Look at the man page via "man picocom" to get the
rest of the story.
Feedback? Questions?
Drop me a line!
Tom's Computer Info / tom@mmto.org