You never know what people will do with your ideas, and it is always fun to see someone do something I would have never thought of with them. I got a video from someone who built my Programmable Fidget Spinner, and used a leaf blower to see how fast they could get it to go. Fortunately, they were wearing safety glasses, and no, it didn't come flying apart. It did, however, start displaying erratically at about 3600 RPM (the fastest I can get it by hand is just under 2000 RPM).
So, what is going on? TLDR: I figured it out and fixed it, and here is proof.
One of the reasons I love Arduino is because it is so easy to write and understand it's code. All the hard parts that scare beginners away have been abstracted out. Case in point, to turn on a single LED, all I have to do is call the function digitalWrite(LED, HIGH). The only thing I need to know is what pin number the LED is attached to and that "HIGH" means "ON" (and "LOW" means "OFF"). To display on the eight LEDs on the fidget spinner, I can use
for (int led=0; led<8; led++) {
digitalWrite(LEDS[led], state[led]);
}
Which means to iterate over the eight pins the LEDs are attached to, one at a time, and turn them on or off based on the value held in the state array. Easy-peazy, and all is well, unless you are doing something that becomes time limiting like turning those LEDs on and off in an ever decreasing amount of time as the figet spinner speeds up. At some point (about 3600 RPM), there just isn't enough time, and the CPU moves on to something else, leaving the LED output garbled.
Now intuitively, I know the Arduino is more than fast enough to do what I want, the problem is that the digitalWrite() function hides a lot of dirty work under the hood, and calling it in a loop only multiplies the problem by eight! When you realize that each of the eight pins is actually an 8-bit port, and that you can set all eight at once using a PORT command, you can increase your speed by almost an order of magnitude.
Of course, there is a lot more detail (and math) to this than I feel qualified to try and explain, but I did try a few timing experiments a while back that probably helped me figure my current conundrum out. Perhaps the most instructive thing to do here is demonstrated how to do the venerable "blink" sketch using ports.
First step in leaving the comfort of Arduino abstaction is figuring out from a Pin-Out chart that Digital Pin 13 (the built-in Arduino LED) is actually bit 5 of Port B on the Atmega328 chip, a.k.a "PB5".
Instead of pinMode(13, OUTPUT); we set the data direction bit of DDRB for bit 7 to 1 using the command DDRB = B00100000;
Now to turn the LED on, we set bit 5 of port B high using PORTB = B00100000, and off using PORTB = B00000000
Realize, off course, that we are controlling all the bits of the port the same time, and that there are clever ways to hit just the bits we want, but I'll save that for a later post.
The most instructive part of this experiment is seeing the difference in the sizes of the regular blink sketch (1030 bytes), and the port manipulation equivalent (634 bytes) is almost 400 bytes! That doesn't seem like much if you are thinking about 32K of memory, but realize that each byte represents an instruction, and you can see we are not just wasting program space, but also using a lot instructions to do the same thing.
I think the bottom line here is to realize that Arduino is great, and abstraction makes life easy, but at some point, you will have to get your hands dirty if you want to go fast!
So, what is going on? TLDR: I figured it out and fixed it, and here is proof.
One of the reasons I love Arduino is because it is so easy to write and understand it's code. All the hard parts that scare beginners away have been abstracted out. Case in point, to turn on a single LED, all I have to do is call the function digitalWrite(LED, HIGH). The only thing I need to know is what pin number the LED is attached to and that "HIGH" means "ON" (and "LOW" means "OFF"). To display on the eight LEDs on the fidget spinner, I can use
for (int led=0; led<8; led++) {
digitalWrite(LEDS[led], state[led]);
}
Which means to iterate over the eight pins the LEDs are attached to, one at a time, and turn them on or off based on the value held in the state array. Easy-peazy, and all is well, unless you are doing something that becomes time limiting like turning those LEDs on and off in an ever decreasing amount of time as the figet spinner speeds up. At some point (about 3600 RPM), there just isn't enough time, and the CPU moves on to something else, leaving the LED output garbled.
Now intuitively, I know the Arduino is more than fast enough to do what I want, the problem is that the digitalWrite() function hides a lot of dirty work under the hood, and calling it in a loop only multiplies the problem by eight! When you realize that each of the eight pins is actually an 8-bit port, and that you can set all eight at once using a PORT command, you can increase your speed by almost an order of magnitude.
Of course, there is a lot more detail (and math) to this than I feel qualified to try and explain, but I did try a few timing experiments a while back that probably helped me figure my current conundrum out. Perhaps the most instructive thing to do here is demonstrated how to do the venerable "blink" sketch using ports.
First step in leaving the comfort of Arduino abstaction is figuring out from a Pin-Out chart that Digital Pin 13 (the built-in Arduino LED) is actually bit 5 of Port B on the Atmega328 chip, a.k.a "PB5".
Instead of pinMode(13, OUTPUT); we set the data direction bit of DDRB for bit 7 to 1 using the command DDRB = B00100000;
Now to turn the LED on, we set bit 5 of port B high using PORTB = B00100000, and off using PORTB = B00000000
Realize, off course, that we are controlling all the bits of the port the same time, and that there are clever ways to hit just the bits we want, but I'll save that for a later post.
The most instructive part of this experiment is seeing the difference in the sizes of the regular blink sketch (1030 bytes), and the port manipulation equivalent (634 bytes) is almost 400 bytes! That doesn't seem like much if you are thinking about 32K of memory, but realize that each byte represents an instruction, and you can see we are not just wasting program space, but also using a lot instructions to do the same thing.
I think the bottom line here is to realize that Arduino is great, and abstraction makes life easy, but at some point, you will have to get your hands dirty if you want to go fast!
No comments:
Post a Comment