To control any more than 12 LEDs on an Arduino board requires a bit of trickery. The Diecimila with its Atmega168 chip has 14 digital output pins, but you somewhat lose two from the TX/RX serial pins on 0 and 1 leaving just 12 pins. Today I built an LED matrix with 20 lights using nine pins,
The technique is to light row by row, and rely on our persistence of vision effect. Playing around with the delay it seemed like 3ms for a total of 15ms (five rows) was about the point where flicker just started being noticeable.
Geeky detail after the fold...
The software pulls the row pins high one by one while the column pins are set low for every LED to light, sinking the current from the one high row pin. There's a 3ms delay, then the next row pin is brought high and the last one low.
The data for the little animation is a 2D byte array that looks like,
byte PATTERN[][5] = { /* ... */ {B0111, B1110, B1100, B1000, B0000,}, /* ... */ };
('B' is a handy prefix for a binary number.)
You can see a diagonal band of 1's there in the early part of the wipe. Each byte's translated into pin writes by looking at the lowest bit in the byte and then shifting right and looking at the next. LOW is on.
void SetColumn(byte pattern) { for (int i = COL_COUNT-1; i >= 0; i--, pattern >>= 1) { digitalWrite(COL_PINS[i], pattern & 1 ? LOW : HIGH); } }
A "frame" of the display is completed by lighting each row in turn,
void ShowPattern(byte pattern[]) { int last_row = ROW_COUNT-1; for (int row = 0; row < ROW_COUNT; last_row = row++) { digitalWrite(ROW_PINS[row], HIGH); digitalWrite(ROW_PINS[last_row], LOW); SetColumn(pattern[row]); delay(MULTIPLEX_DELAY_MS); } }
The main loop draws each frame over and over until a certain amount of time has elapsed and then it goes onto the next one. millis() is a built-in that returns the number of milliseconds since the board was switched on. (This code would fail after about nine hours when the counter resets.)
void loop() { for (int pos = 0; pos < PATTERN_SIZE; pos++) { long start = millis(); while (millis() - start < PATTERN_DELAY_MS) { ShowPattern(PATTERN[pos]); } } }
I actually had the circuit the other way around, cycling through column-by-column and the rows programmed in turn with a line of lights. This happened out to make writing patterns awkward so I flipped the orientation which made SetColumn pretty straightforward.
The matrix could've had more lights on it but the breadboard limited width a bit. I was quite proud of the tight wiring under the LEDS.
From Multiplexed LED Matrix |
This was one of the projects where the bits that seemed like they'd be hard Just Worked and then silly things wasted time, like C mistakes with sizeof.
int ROW_PINS[] = {13, 12, 11, 10, 9}; int COL_PINS[] = {5, 4, 3, 2}; #define ROW_COUNT (sizeof(ROW_PINS)/sizeof(ROW_PINS[0])) #define COL_COUNT (sizeof(COL_PINS)/sizeof(COL_PINS[0]))
Parameterizing these kinds of things made moving from a 3x4 board to 4x5, flipping the rows & columns, and using different pins trivial. An unfortunate cargo-cult effect in Arduino and Wiring code I've seen is folks not using #define and sizeof.
Some cute enhancements might be to have an array of structs, the struct containing additionally a delay time for each frame. We could also have, rather than 0/1, a wider range of values to indicate brightness. With that, (e.g. Perl/Python scripted) code could generate the animation sequences rapidly flipping frames to give an impression of anti-alias. Unfortunately I have other things to do... (yeah, I made the animations by hand in vi and pasted into the Arduino editor :-))
And finally, a Wiring-based pair of color LED matrixes are used as toilet signs... that change throughout the night [video]. More on this funny project.
I've spent a very pleasant Sunday afternoon playing with an Arduino microcontroller board. After a trip to Maplin I plugged together a little array of assorted LEDs from their "lucky bags" and got hacking.
The Arduino board is fabulous: for an amazingly cheap €22 you get a USB-programmable 16Kb microcontroller with a bunch of digital and analog input and outputs, and the outputs are capable of driving useful things like LEDs. The software's not just free but open source too--and it's decent, with excellent documentation. I'd say from downloading the software and getting my first "hello world" program loaded was about five minutes. It Just Worked™. This might not sound like a big deal, but interfacing to computers has generally been expensive, esoteric, fiddly, and error-prone. The Arduino board is none of these.
The tutorials are great and introduce the important concepts with example circuits and code. In fact, the set of example code is all accessible directly from the Arduino app's main menu, which is a nice touch. (I.e. you don't have to dig around trying to open a file buried on your hard drive.)
So anyway here it is. Eleven LEDs connected to digital pins 0-10, and a 22k linear pot (variable resistor) feeding analog pin 5. The LEDs "chase" up and down with a speed taken from the pot position.
It was fairly painless, at least after an initial scare where programs wouldn't upload,
avrdude: stk500_recv(): programmer is not responding avrdude: stk500_recv(): programmer is not responding
I read all sorts of scary solutions online where folks were reprogramming their bootloaders and other horrors. One Arduino hacker mentioned running the board off an external power supply (rather than rely on USB) during programming as the current draw is apparently higher. This was enough for me to try pull the LED's ground pin out so the lights turned off: Bingo! No problem programming. It became a bit annoying pulling the ground lead out every program: next trip to Maplin I get a switch...
The other issue I had was remembering how C does casting. My function to scale the analog input into a minimum and maximum delay was being simultaneously confused by my code not casting up to a float early enough, and a persistently dodgy connection with the pot.
(The fifth pin/LED didn't seem to light very strongly. Tried different LEDs and lower resistance. Didn't fix it or figure out why it was dim; I'll try a different board soon.)
So what led me to Arduino? The brand new Science Gallery at Trinity College in Dublin is running a programme to introduce high school kids who wouldn't otherwise have had the educational opportunity to do so to learn about electronics. I'm helping out there on Wednesday afternoons, which so far has mostly involved teaching them how to solder, and debugging electronics problems. Where possible I have them learn from my mistakes: my room in my parents' house, for example, was so badly burnt from soldering iron accidents and riddled with little lumps of solder they had to replace it after I left... (I suppose one useful legacy I left was installing about two dozen plug sockets :-))
So far the kids have created some very cool LED pictures each with a fierce amount of soldering and now are ready to hook them up to Arduinos. Hence the need to stay one step ahead of the younger generation ;-)
#define LED_COUNT 11 #define DELAY_MIN 10 #define DELAY_MAX 400 #define DELAY_STEP 50 #define POT_PIN 5 #define INTERNAL_LED_PIN 13 void setup() { for (int i = 0; i < LED_COUNT; i++) { pinMode(i, OUTPUT); digitalWrite(i, LOW); } pinMode(POT_PIN, INPUT); pinMode(INTERNAL_LED_PIN, OUTPUT); } int ScaleDelay(int in) /* Calculates a delay between DELAY_{MIN,MAX} from an analogRead. */ { float d = (DELAY_MAX - DELAY_MIN) * (float)in; d = d / 1024 + DELAY_MIN; return (int)d; } void LightLED(int led_pin, int last) /* Reads a delay, lights an LED on led_pin; turns off the last one a short time later. */ { int d = ScaleDelay(analogRead(POT_PIN)); digitalWrite(led_pin, HIGH); delay(d / 10); digitalWrite(last, LOW); delay(d); } #define TAIL_LENGTH 3 int tail[TAIL_LENGTH]; int tail_pos = 0; int LastLED(int current) /* Maintains a list of lit LEDs. Returns the very last one. */ { tail[tail_pos] = current; tail_pos = (tail_pos + 1) % TAIL_LENGTH; return tail[tail_pos]; } void loop() { int last; for (int i = 0; i < LED_COUNT; ++i) { last = LastLED(i); LightLED(i, last); } digitalWrite(INTERNAL_LED_PIN, HIGH); for (int i = LED_COUNT-1; i >= 0; --i) { last = LastLED(i); LightLED(i, last); } digitalWrite(INTERNAL_LED_PIN, LOW); }
There was a neat idea recently on the BBC backstage list to produce a tag cloud of words in subject lines. There was an implementation of this but as usual with almost everything posted on Backstage no code.
It's frustrating that the BBC go to all the trouble of making their data available and then developers horde their little snippets of code. I just don't understand it. Big kudos to MighTyV who do open source their code. And deservedly won the BBC Backstage competition.
So, without further ado, some code to implement tag clouds off mailing list subject lines:
#!/usr/bin/perl use warnings; use strict; use Email::Folder; use HTML::TagCloud; my $mbox = shift || "$ENV{HOME}/Mail/Lists/Backstage"; my $cloud = HTML::TagCloud->new; my $folder = Email::Folder->new($mbox); my %word_count; foreach my $subject (map { $_->header("Subject") } $folder->messages) { my @words = grep !/^(to|you|we|the|and|would|on)$/, grep /^\w+$/, split ' ', $subject; $word_count{$_}++ for @words; } $cloud->add($_, 'http://realprogrammers.com/', $word_count{$_}) for keys %word_count; print $cloud->html_and_css(50);
(I'm happy to comment this for anyone wishing to play with it.)
Back in Aug of 2004, I switched my blog to use descriptive URLs rather than the old-style, Movable Type 2 default of /archive/002981.html
.
The downside to this is that after the site rebuild I had effectively created a whole 'nother blog, one all crosslinked in the new style, and one in the old style. And because the rest of my site has random links into the old style from before the change it continues to be crawled. To this day I still get search referrals pointing to the old site.
This isn't so bad except for two things. Comments go to the new site, not the new one. So referrals where someone's looking for rent scam help would, on the old site, miss out on all the useful (and entertaining in some cases) comments. The other bad thing is that the old site isn't running ads. OK, you might think that's a good thing :-)
So tonight I decided to fix this. Here's how I did it.
The strategy I took was to produce a set of redirects from the old site to the new site. The best way to do a redirect is at the webserver level as it teaches crawlers and browsers where the new pages are. There are in fact two types of redirects, temporary (aka "302") and permanent (aka "301"). Apart from being correct, the latter is also the preferred-by-search-engines method--tales abound of sites losing PageRank for using 302 temporaries.
Using the Apache webserver, I needed to produce a whole pile of RedirectPermanent directives to be included in paulm.com's Apache configuration.
Here's the answer,
export PERL5LIB=/home/mt/cgi-bin/lib
perl -MMT -MMT::Blog -le '
$mt = MT->new(Config=>"/home/mt/cgi-bin/mt-config.cgi");
$b = MT::Blog->load(13);
@e = MT::Entry->load({blog_id=>13});
for $e (@e) {
printf "RedirectPermanent /inchoate/archives/%06d.html %s\n", $e->id, $e->archive_url;
}' > /etc/apache2/paulm.com-redirect.conf
OK, what's going on here. First we need to create an instance of the top-level MT
object. (Here we see an awesome example of how to abuse Perl's object model--subsequent object instantiations, e.g. MT::Blog
make no reference at all to that MT instance, it all just magically works. Ah, pixie dust.)
Next up we load up my blog object. I know its id is 13 by looking in the mt.cgi
URL: blog_id=13
. Alternatively you can look in the mt_blog
database table. I don't actually need this line of code as it turns out I can pull the blog entries out directly. I decided to leave this line in there for some extra documentation and reference.
So the load()
method works with several MT types, the ones sublcassed from MT::Object
, and has a fairly rich interface. This example shows loading all the entries of blog_id 13. Reassuring when I print scalar(@e)
I got 336 which is the same number as reported on my blog dashboard page.
After divining that MT::Entry::archive_url
was the right method for printing an entry's URL the ball's in the net. The final piece was manually constructing the old-style URL using printf "%06d"
which says "print this decimal zero-padded to six digits".
Now in my /etc/apache2/sites-available/paulm.com
I simply added,
Include /etc/apache2/paulm.com-redirect.conf
apache2ctl config && apache2ctl graceful
(&&
is an improvement on ;
in that it'll only execute the next command if the first succeeded.)
Finally I of course needed to test it worked, paulm.com/inchoate/archives/002950.html. Yep!
And in the logs,
perl -lane 'print if $F[8] == 301 and $F[6] =~ /\d{6}/' /var/log/apache2/paulm.com-access.log
x.x.x.x - - [29/Oct/2006:23:46:20 +0000] "GET /inchoate/archives/002919.html HTTP/1.1" 301 279 "-" "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7"
paulm.com-redirect.conf
. One option might be to re-run the script above and put the date as a constraint in the MT::Entry->load()
call. This would require me to figure out how to do that and, being lazy, I just can't be bothered (if you know, feel free to leave a comment). So I'm going to throw my sys-admin skills at the job instead.
But... I can feel a two-in-one trickshot coming on. But to set up this trickshot, another observation:
I still have "two" blogs. I need to remove those old pages. It would be easy to just go ahead and wipe them out with rm inchoate/archives/00*.html
but I'm curious to see if anything's left after I remove only we know about.
Can you see the trickshot? I'm going to remove the unnecessary redirects and at the same time remove the old files, all in a one-liner. So, I re-used the results of the last bit of code (redirect.conf) pulling out the old-style URLs (/inchoate/archives/002918.html
) and turning them into their place on the filesystem. Now I can test to see if they exist and if so, remove them and keep the redirect line, and if not, remove the redirect line.
perl -ani~ -e '-f ($f = "/home/www/paulm/paulm.com$F[1]") and unlink($f), print' /etc/apache2/paulm.com-redirect.conf
This scary bit of code does an in-place modification (-i
) of the redirect file saving a copy with the ~
extension in case I make a mistake. I make use of the surprisingly little-used -a
switch which splits the line into the @F
array ('a' for 'awk' which by default has these in $1
, $2
, etc). So $F[1]
is the old-style URL. If the file exists, the -f
test, then unlink (remove) it and print the line. The effect of printing the line retains it from file.
So as an aside we see two perl one-liner idioms,
perl -ni~ e 'print if $some_condition'
to remove lines except some and perl -pi~ -e 'do_something_with_each_line(); # typically an s///'
Back to the job: I want to observe what happens, so I take before and after shots with ls $paulm/inchoate/archives/00*.html; wc -l /etc/apache2/paulm.com-redirect.conf
. I.e. take the before shot, run the above perl line, and re-run the ls; wc -l
"after" shot. For the latter I get, reassuringly,
ls: /home/www/paulm/paulm.com/inchoate/archives/00*.html: No such file or directory
47 /etc/apache2/paulm.com-redirect.conf
Since this has changed my apache configs another webserver kick's needed.
It's all well changing the entries but what about the old-style archives/2004_08.html
? Laziness prevailing here again I checked the last week's logs for references,
ls $paulm/inchoate/archives/200*_*.html # this revealed four from 2004, hence: grep archives/2004_0 $logs
Nothing. Delete 'em: rm $paulm/inchoate/archive/200*_*.html
The same wasn't true for categories unfortunately,
grep cat_ $logs
cat_*.html
pages.
The old RSS feeds could go, rm $paulm/inchoate/archives/[0-9]*.xml $paulm/inchoate/[0-9]*.xml
My site's built from little XML fragments and full HTML pages, so I'm going to look in all of them and switch references to the old style,
find $paulm \( -name '*.html' -o -name '*.xml' \) | xargs grep -l 'inchoate/archives/00' | tee /tmp/old-style-pages
There were tantalisingly few there (about four)--I was sorely tempted to hand-edit them. But no, see if I can whip out another one-liner in less time than it takes to look each one up:
</tmp/old-style-pages xargs perl -pi~ -MFile::Slurp=read_file -le '
BEGIN {
%h = map { (split)[1 => 2] } read_file("/etc/apache2/paulm.com-redirect.conf");
s~http://paulm.com~~ for values %h;
$re=join "|", map "(?:http://paulm.com)?$_", keys %h
};
s/($re)/$h{$1}/ge'
The neat trick here is that I'm doing an in-place file change while using the BEGIN
clause to slurp in the redirect file and make a hash mapping the old style pages ("word" 1 of the line) to the new URL ("word" 2). Skipping the next line for a second I create a big regex to match any reference the old-style URLs.
Now those mystery references to http://paulm.com: This is quite subtle. The references in the script to my base URL http://paulm.com
combine to remove that from the URLs in the pages. By not replacing with the full URL, the script also works on matches on filesystem references, e.g. /home/www/paulm/paulm.com/inchoate/archives/002918.html
. Without it you'd end up with /home/www/paulm/paulm.comhttp://paulm.com/inchoate...
(I didn't get this the first time round; only after I ran the script on one page and noticed it on a file that did an INCLUDE
of a blog entry).
(By the way, if you're thinking, my god, who writes scripts like that off the top of his head? The answer is, if you're prepared to experiment, play, and believe it's possible: "you")
The test for success here is no 404s (missing pages), just 301s (permanent redirects). So over the next few days I'll be keeping an eye on my server logs using techniques shown above, and tail -f
the error log in a window.
Changing one's URL scheme is not something to be undertaken lightly! However, if you do choose to, having a blogging engine that enables relatively straightforward construction of scripts and one-liners to manipulate its data renders the job a matter of a half-hour or so of reading docs and experimentation.
And of course behold the power of perl and unix to perform sophisticated data transformations and get the job done!
Back in April, on my birthday even, I put out a little .NET application that helped in timing intervals for training. In particular it defaults to the tricky-to-time Tabata whose brutal schedule consists of eight 20s-on, 10s-off periods (i.e. 3:50 mins total). At the time it didn't work without the as-yet-unreleased .NET 2005 Beta and it wouldn't work on 2003 and I couldn't get my device to work and ... anyway. It's all working again now,
IntervalTimer-20051221.exe (11K)
Copy this onto your device e.g. in \Program Files (I don't know how to do Pocket PC installers yet) and then run it from File Explorer. This ought to execute on a desktop machine too; it does on my XP box here.
N.B. This version includes a 10s countdown after hitting Start. Then the pain starts. The lurid colours are deliberately full-screen like that so you can see the changes when your device is a way away from whatever punishment you're dishing out to yourself.
Enjoy!