-
On the limits of Arduino...
12/04/2016 at 17:10 • 1 commentAs I go along with this project, some of my logs will be not so much to inform you as to what to do to reproduce my project, but rather to explain why I did some of the things I did in a more philosophical sense.
A fair number of my friends of roll their eyes when I mention Arduino projects. To them, the Arduino represents "amateur hour" in the tinkering universe. In their eyes, Arduino is the kind of thing that you teach to nine year olds, but discard when you become a teenager.
I think that does the Arduino a disservice. I think there are many reasons to like the Arduino:
- I can strum a few tunes out on the ukulele, but I doubt I'll ever have enough time to be truly proficient. That does not mean it can't be fun and rewarding to try, and over time I may learn more than seems obvious to me today. In trying to learn to programming, it's hard to see where the baby steps we may take will lead us, but if we are going to make any projects, we need to take those baby steps.
- The Arduino community is awesome. Lots of people share libraries, codes, and ideas for projects. People answer questions online, and post videos of their projects too.
- Arduino programming tools are free and available on every major platform.
- Arduino is popular enough that a whole ecosystem of board variants and shields can be found, which makes prototyping new ideas with sensors and displays easy.
All great stuff, but there are a few drawbacks to doing things the "Arduino way."
The Arduino framework can limit performance
The Arduino software framework provides a high level and easy to understand interface to the hardware capabilities of the Arduino board. For instance, each pin on the Arduino is abstracted as an individual numbered resource. You can tell each pin whether it is to serve as an input or an output using pinMode, and then use digitalWrite to set it to either be HIGH or LOW. If I use a Uno, a Trinket, or even one of the Arduino Mega boards, my code will work without change. As long as you have the appropriately labeled pin, it will work.
But this abstraction isn't how the real machine is organized. Each pin is part of a "port", which is a collection of eight pins. They might bear names like PORTB, and have numbered pins with names like PB_7. On different types of Arduinos, a pin number like "5" may be wired to a different pin on a different port. The Arduino framework translates the simple "5" into the appropriate port and pin number, depending on what type of Arduino you've told to compile for.
But this translation can be slow.
I wrote up some exploration of this in this blog post about using digitalWrite to drive a simple 7 segment LED display. This display is pretty simple to implement. It requires three pins. To start sending data, you bring the "latch" pin LOW. You then send which digit you want, starting with the most significant bit by:
- setting the data pin to the appropriate value
- setting the clock pin high
- resetting the clock pin low.
You then repeat the process to send which segments you want to turn on, and when you are finished, you set the latch back high, and the digits update.
Here's the code implemented in canonical Arduino framework style:
void LED_irq(void) { digitalWrite(latchpin, LOW); // select the digit... shiftOut(datapin, clockpin, MSBFIRST, col[segcnt]) ; // and select the segments shiftOut(datapin, clockpin, MSBFIRST, segbuf[segcnt]) ; digitalWrite(latchpin, HIGH) ; segcnt ++ ; segcnt &= NDIGITS_MASK ; }
When I first set this up, I hooked up the latch pin to my oscilloscope so I could time the total amount of time that it would take to execute. I found out it took about 320 microseconds when running at a 16Mhz clock rate like on the classic Uno. I set this up to run every 2ms, which means it takes up about 16% of available cycles. That's fine, but if I wanted to run it on an 8Mhz, it would be consuming 32% of available cycles, and I couldn't think about running it at 1Mhz.But there are faster ways to implement the same idea. The digitalWrite and shiftOut calls are really just wrappers for modifications to the PORTB data direction and data registers. They can be implemented much faster (and considerably uglier) using direct access to those registers. If this doesn't make sense to you, then don't panic, but here's the faster implementation of the same code.
void LED_irq(void) { PORTB &= ~0b00010000 ; for (int i=0; i<8; i++) { if (col[segcnt] & (0x80 >> i)) PORTB |= 0b00000100 ; else PORTB &= ~0b00000100 ; PORTB |= 0b00001000 ; PORTB &= ~0b00001000 ; } for (int i=0; i<8; i++) { if (segbuf[segcnt] & (0x80 >> i)) PORTB |= 0b00000100 ; else PORTB &= ~0b00000100 ; PORTB |= 0b00001000 ; PORTB &= ~0b00001000 ; } PORTB |= 0b00010000 ; segcnt ++ ; segcnt &= NDIGITS_MASK ; }
Yes, it could use some additional work to explain, but if you understand that PORTB represents 8 pins at once, and can decipher C's rather inscrutable binary arithmetic, you can see that this pretty much the same code. Except that it runs about 8x faster, only about 48 microseconds out of every 2 milliseconds, or about 2.4% of available CPU. But now, you have the option of slowing down the clock. Running at just 1Mhz, it would be about 38.4%. Yeah, that's still a lot, but it's doable.Incidently, a simple sketch that drives this 8 digit display can be found on my github repository.
We'll revisit this when I show you the code for driving the WS2812 LED chains, which like our LED display, can require some careful timing.
The Arduino framework doesn't really help you understand interrupts
For the most part, the Arduino framework is at its best when it works by polling. If you have an application that reads some switches, and does some stuff in response, then your inner loop might look like this:
void loop() { for (;;) { if (readswitchA()) doswitchAstuff() ; if (readswitchB()) doswitchBstuff() ; delay(500) ; // wait for half a second... } }
That's not bad, but there are a few problems with this kind of programming. Let's say that the doswitchAstuff() takes several seconds (or even longer to execute) but that you really want to make sure you react to switchB as fast as possible (perhaps it is the "shut down the robot as fast as possible before it eats a child" button.) It won't examine switchB until after you've done the switchA stuff, so little Sally could be halfway down the steel gullet of Robosaurus Rex before we examine it again.
What we really would like is that if switchB was thrown, wherever the program is in execution, it interrupts whatever it was doing and "handles" this situation. Such conditions are called appropriately called "interrupts" and the routines they execute are called "interrupt handlers". They are really useful. It means that I can go ahead and write doswitchAstuff() however I like, and yet I don't need to wait to check switchB to know if it was thrown. The hardware itself will monitor switchB, and if it changes state it will automatically call an ISR (interrupt service routine) to handle the situation.
Beginning programmers can have a difficult time understanding interrupts. The Arduino framework basically tries to pretend they don't exist, and don't provide much help in setting them up and using them.
Individual libraries can use them and provide an abstraction that hides the complexities from you. A good example is the TimerOne library that I used in the clock8 example that I linked above. Every 2ms, I want to update the state of one of the digits of my clock. The TimerOne library uses a hardware timer to execute the update code (LEDirq in the example) every 2ms. I can go about writing my normal loop() code as I would. I don't need to poll for the right conditions to fire my code: it happens more or less automatically.
But really interrupts aren't all that hard to understand, and with a little work, you can use them in our own code. I rather wish that the Arduino framework made some effort to provide support for them, because it would allow Arduino programmers to write more powerful programs, but with a little effort, we can set them up and achieve some coolness that would be hard to achieve without them.
The Arduino framework doesn't provide protected abstract access to hardware resources
This is a bit more abstract, but important. When you first start using an Arduino, it's easy just to think that the machine is actually something called "an Arduino". But in reality, that's just an abstract view of a real, hardware processor. The processor that is used in the Arduino Uno is made by Atmel, and is called the ATmega328p. If you google for that term, you can find the datasheet which tells you all about the low level details of the chip. PDF of Atmel ATmega328 datasheet
If you scan the sheet, you can find out about some of the hardware peripherals it provides.
I'll just concentrate on the first two, which indicate that the ATmega328p has three different Timer modules. When we were talking about interrupts above, one of the ideas was that you could use a Timer to execute code at different intervals by providing periodic interrupts. But these Timer modules can also do other things. They can (for instance) count pulses, which might be good for your application. Instead of polling and watching for each transition from HIGH to LOW to HIGH again (and possibly miss one if you are off busy doing something else) you can just let the hardware Timer keep track of these counts for you, and read the total out when you need to.
But again, the Arduino framework hides Timers from you. If you read the datasheet, you'll see that these timers are labelled Timer0, Timer1, and Timer2. Timer0 and Timer2 are the eight bit timers, and Timer1 is a 16 bit timer. You might think that you can just go ahead and use them.
But, there is a gotcha. Timer0 is already being used to implement the Arduino delay(), millis() and micros() functions. If you try to use Timer0 for your own code, then you'll have to avoid using those functions.
Similarly, Timer1 is used by the common Servo library, and Timer2 is used to implement the tone() function. Let's say you are working on a system to control a radio controlled quadcopter. You want to be able to use delay, so you shouldn't try to use Timer0. You also would like to use the Servo library, so Timer1 is out. No problem, think you! Timer2 is only used by tone(), and I don't use tone(), so I can use it.
But there are problems. Perhaps there is a library that you want to use that does use tone() for some reason you didn't know about. Or perhaps that library uses Timer2 for an entirely different purpose (after all, you chose it for that reason as well.)
This wouldn't be bad if the compilation process told you "hey, there is some contention for the use of Timer2", but there is no such process. Both bits of code can access the hardware registers that control Timer2, perhaps one using it to trigger interrupts, and the other trying to count pulses, and nothing good will happen.
What this means is that sometimes you have to be pretty careful in looking at libraries and how they are used. As you get to be a more experienced Arduino programmer, you'll find that you have to look inside the code for libraries and learn about the hard details of what they are trying to shield you from. And, you have to be careful in how you use the hardware resources at your disposal. But with this experience will also come power. You'll actually understand how these things work, and that will enable you to do things that you didn't know how to do before.
Addendum
Okay, that was sufficiently abstract and boring that I bet nobody made it this far. The next installment will be back to writing code to blink LEDs. Thanks for listening.
-
My new favorite tool: platformio
12/04/2016 at 05:44 • 0 commentsThis is an aside from the actual project, but I thought I would go ahead and mention a tool which has become one of my favorites: platformio.
If you are the sort of person who is reading about this project, chances are good that you are using the Arduino IDE to do a lot of your programming. The Arduino IDE has a lot going for it. It is available on Linux, Windows and Mac, and it works the same pretty much everywhere. In the most recent upgrade, it supports a fair number of architectures, and has a library manager as well. For many of you, it will be a comfortable and familiar download, and requires only the installation of a single application.
But I'm kind of old school.
I cut my teeth on Unix boxes. I have a Macbook that I use for a lot of my programming, but I spend the vast majority of my time in Terminal windows, using familiar utilities like ls, cat and vi. And, I mostly don't like to use a mouse all that much. I like to use utilities like make.
But what I don't enjoy is downloading and installing all the necessary cross compiling tools to compile code for the Arduino. The Arduino IDE is really just a wrapper around the GNU C compiler. You could use make and avr-gcc without the IDE environment, but setting that up can be a pain, and its hard to keep it up to date.
Until, that is, until platformio was created.
It is a system written in Python that allows you to compile code for the Arduino and many other systems (like the Teensy, the ESP8266, and mbed processors) all with just a simple set of consistent commands. It will go off and find all the command line tools you need for the board you've selected, and then install them so that you can compile code with just a simple set of commands. Since many boards like the ESP8266 have some compatibility with Arduino code, often you can easily recompile the same code to be executed on different boards, all at once.
When I eventually make my code available on github, I will include the necessary files to compile with platformio. You can still compile any of the Arduino sketches using the IDE if you like, but I urge you to give platformio a try. In the end, I'll show how you can use it to compile barebones C code for processors like the ATtiny13, as well as some other tricks like disassembling C code to learn more about how the machine language code generated by gcc can be modified to be more compact and to understand other optimizations.
-
I'd write you a shorter letter if I only had the time.
12/03/2016 at 21:39 • 0 commentsWhen you are writing prose, brevity can be difficult. You have to think very carefully about how to express your ideas concisely and achieve maximum impact. Often, it's simply easier to dump the contents of your brain, and just keep writing and writing, and telling stories and engaging in diversions and pretty soon you realize that you are just continuing to ramble, not just about how to do LED programming compactly, but on the meaning of Christmas, about how you wore your blinking Santa hat, and this one woman with a full grocery cart and a screaming child and a tired look on her face was behind you when you were just picking up a carton of milk, and yet managed a smile, and about how you immediately allowed her to cut in front of you because hey, that's the way it is during the holidays, and I showed her pictures of my grand daughter, and now as I think about it I have two grand daughters and I miss them especially now that it's nearing Christmas time...
You get the idea.
The fact is that the most direct path to getting a basic Christmas LED blinker is to stand on the shoulders of giants, and just use the hardware and software that others have used before you. The most obvious and least error prone way would probably be to buy a real Arduino from the official suppliers or from Adafruit for around $25, download the standard Arduino development environment, then use Adafruit's awesome NeoPixel library and tutorials. Indeed, that's just what I did. As my first program I decided to do something simple. I'd write a program that would display a repeating pattern of eight colors along the entire length of 240 LEDs that I had, and that a few times a second it would shift the LEDs, and then redisplay the pattern. It didn't take me very long (I spent more time finding a spare Arduino and the appropriate USB cable). Here's the resulting code:
#include <Adafruit_NeoPixel.h> const int PIN = 5 ; const int NLED = 240 ; // Create an interface to the Adafruit library, which is // very cool and very convenient... Adafruit_NeoPixel strip = Adafruit_NeoPixel(240, PIN, NEO_GRB + NEO_KHZ800); void setup() { strip.begin() ; strip.show() ; } #define LVAL (0) #define HVAL (64) int colortab[] = { LVAL, LVAL, LVAL, // BLACK HVAL, LVAL, LVAL, // RED LVAL, HVAL, LVAL, // GREEN LVAL, LVAL, HVAL, // BLUE HVAL, HVAL, LVAL, // YELLOW LVAL, HVAL, HVAL, // CYAN HVAL, LVAL, HVAL, // MAGENTA HVAL, HVAL, HVAL, // WHITE } ; void loop() { int start = 0 ; int offset, i ; for (;;) { offset = start ; for (i=0; i<NLED; i++) { strip.setPixelColor(i, colortab[offset], colortab[offset+1], colortab[offset+2]) ; offset += 3 ; if (offset >= 24) offset = 0 ; } strip.show() ; delay(100) ; start += 3 ; if (start >= 24) start = 0 ; } }
Yeah, it's not commented, because it seems pretty obvious to me. If it's not to you, then go read Adafruit's tutorial some more. Basically the colortab array contains R, G, B values for 8 different colors, and the main loop starts by copying entries from that table into the strip using the simple strip.setPixelColor interface. Most of the logic involves handling the wrap around and replication of color values. When all the LEDs have been updated, then it calls the strip.show() function to tell them all to update. It then updates, delays for 100 milliseconds, and then repeats.
It works. It works just fine. If you want, you could skip onto using code like this, or modifying it to do different patterns, timing, etc.
But...
Here is the thing: it's on a $25 Arduino Uno board, which is physically large, and fairly expensive.
First, let's address the size. One of the great things about the Arduino is that there are a variety of boards which are compatible with one another. The same code could conceivably be run on those boards too. For instance, Adafruit sells the Metro Mini for a mere $12.50, which is tiny (just 0.7" x 1.7") and is in most respects a completely compatible replica of the larger and more expensive Uno board. These are great. They include the necessary interface to use USB to power them and communicate with them for programming. If you are really trying to pinch pennies, you can get imported boards from China, like these from Banggood, which cost just $10.20 for five of them. Yes, just $2 each. These don't include the USB interface, but you can buy one separately for about $5.
Adafruit also stocks a series of cheap boards based upon a smaller cousin of the ATmega328 chip that powers the Uno. The Adafruit Trinket uses an ATtiny85, which costs just $7, and has just 8K of flash and only 512 bytes of RAM memory (compared to the 32K of flash and the 2K of RAM in the standard Arduino.) These boards are not entirely compatible with the Uno, but you can program them using the same and IDE and if you are careful, you can use many libraries on them as well.
Whether you choose to support some of the terrific vendors like Adafruit or Sparkfun who provide a lot of awesome libraries and support, or scrimp a few pennies and roll the dice with super chip modules from China (I do both) you could use the code above to drive these LEDs.
But...
There are a few issues with the code. First of all, it's not all that tiny. Sure, it fits pretty easily into Arduino. When it compiles, it reports the following:
Program: 2568 bytes (7.8% Full) (.text + .data + .bootloader) Data: 88 bytes (4.3% Full) (.data + .bss + .noinit)
We have lots and lots of space! Only 7.8% full!
But there is a hidden issue. This does not report the memory that is allocated by the NeoPixel library at run time. Adafruit's library is awesome and flexible, because it allows you to update each pixel in any order you like. You can even read back the values that you stored there, which is nice when you try to fade them smoothly to black over time. But that means that you need 3 bytes of storage for each LED in the system. Since my 4m strand has 240 LEDs, that means that I need 3 * 240 bytes (about 720 bytes) or memory to store those values. That's over 1/3 of the total RAM available, which again, doesn't sound too bad. But perhaps you need that RAM for something else. Or perhaps you want to use one of those Adafruit Trinket's, which only has 512 bytes of memory. What can you do?
Well, that's what this project is all about. It is about being clever about making cool blinky light displays that use very little memory. You could use the Trinket or perhaps just a lowly ATtiny13A which costs just $1.25 and has only 1K of flash and just 64 bytes of ram to make cool, blinking displays. That you can use to make a Santa hat. I am going to show how I used the awesome info (and code!) I gleaned from @joshlevine about how to drive these RGB strips to make the same display that took just 2568 bytes of flash and 720+ bytes of RAM to run in less that 300 bytes of flash and maybe only four bytes of RAM. I'll then do a couple of additional tricks to show how patterns which may not appear to be so simple can still be done on tiny processors.
Feel free to leave comments and discussion, I'll try to answer them as I go along.
-
Introduction...
12/03/2016 at 18:33 • 0 commentsBack in 2011, I bought a simple battery powered strand of LEDS at a local pharmacy. They were powered by a trio of double A batteries, and consisted of two independent strings, one which had red and green, the other which had blue and yellow. But they just well, turned on and off. I thought I could do better. I had just finished my ATtiny13 "blinking pumpkin" project.
And decided that I could use the same basic ideas to implement a set of blinking lights that I could put inside a Santa hat, and wear around to bring Christmas cheer. I documented this in a series of Youtube videos at the time, as well as a blog post where I also shared the (very simple, nearly trivial) code.
http://brainwagon.org/2011/12/16/christmas-lights-powered-by-an-attiny13/
http://brainwagon.org/2011/12/18/project-completed-my-99-christmas-led-hat-with-attiny13-controller/
Look, this is kind of a ridiculous thing to do. You can probably find something to do this off the shelf, and with less muss and fuss than it will be to reproduce my project. But this is what I do for fun, so don't judge me!
I pulled my hat out of storage while setting up my Christmas tree this year, and found out that for some reason, one of the strand of LEDs has stopped working. No doubt I just have a bad connection somewhere, and I could fix it easily. But I thought that maybe it would be a good time to revisit this project using the same processor family, but perhaps with some fancier LEDs. Notably, I've been looking for an excuse to order a long strand of RGB addressable LEDs, where each LED's color can be controlled independently. So, a little surfing on amazon.com, and I ordered four meters of RGB LEDs. I also thought it was an excellent opportunity to use the platformio system to highlight how you could do "bare metal" programming that doesn't rely on a lot of Arduino libraries and infrastructure, and thus would allow you to use very small, very resource constrained processors like the 8 pin, ATtiny13. And, I wanted to highlight the excellent technical work of excellent work on NeoPixel timing over at josh.com, which makes it possible to drive these LEDs in just a few hundred bytes of code.
My goal is not so much to deliver firmware that you will download and use without much thought, but rather to use the project to demonstrate some of the thinking that I use to do simple projects like this. That it coincides with the Hackaday 1K code competition is just gravy. Writing small programs for resourced constrained processors is just fun. Blaise Pascal once famously wrote to a friend that he would have written a shorter letter if he had more time. Writing small programs requires the kind of puzzle solving skills that often don't get exercised with longer endeavors. I hope that this project serves to inspire you to consider that using tiny processors to do simple tasks can be fun and rewarding.
As a teaser, here is some (terrible) cell phone video that is as yet unexplained, but will serve to show what is possible. I've only used 25% of the available flash on the chip, with code written in C and some inline assembler, and there is lots more to come. Hope you enjoy!