Thursday, July 3, 2008

Tying It All Together into a Clock

So after my last few posts, the next step in the project almost seems enevitable.  Combine the Arduino, LCD, and DS1306+ chip together to make a clock.  Of course Jeff was thouroghly involved with this project, as usual.


This project took us about 1.5-2 hours to figure out, with lots of totally random errors that we couldn't explain.

So first off, Jeff wrote up the code to catch the interupt every second and increment the seconds, minutes, hours, and am/pm correctly.  This was pretty easy since I already figured out all the snafus with the DS1306+ chip.  It wasn't quite how I would have written it, but it worked on the first run anyways.

Meanwhile, I was working on trying to figure out the hardware.  The LCD took up almost all the pins on the Arduino, not leaving me much to work with.  We tried moving the Enable pin from 2 to 13 to free up one of the interupt pins, but no dice.  The library was good enough to not usse magic values for each pin, but made many many grave assumptions about which order all of the pins are in, which we never managed to work around.

After hacking on that for quite some time, we dumped the LiquidCrystal library and went with the LCD4Bit library instead, since it only used a 4 bit bus to talk to the HD44780 and happened to not use pin 3, which is the second interupt pin.  Glancing over the documentation yet again screwed me, and I missed the needed grounding of the R/W pin until the very end.  Other than that, it was an almosst complete dropin replacement, with nice features like turning on the second line for us.  We removed the DB0-DB3 lines, and moved forward.

Meanwhile, Jeff was trying to debug the rest of the program, using the serial port instead of the LCD to read the output.  For some reason, calling the half-hacked LiquidCrystal init function in setup() killed the interupt after the first trigger, even though lcd.init() was returning well before the first trigger.  This was never explained and fixed when we switched over to LCD4Bit.

So now for the final moment.  Bring together the last hour of hacking and frustration, and it won't compile.  Hex file .../clock.hex does not exist.  And sure enough, it didn't exist.  we comment out lcd.init(), and it compiles fine again.  Doesn't make any sense.  Eventually, I happened to delete some old commented out code at the bottom that Jeff was using as a reference, and it started compiling again.  No explanation as to why.  That code had been there the entire time.

So now for the features I am most proud of:
The clock is set over the serial line, so you have to plug this clock into your computer to set it (but what fun would a nerds clock be otherwise?).  You just need to send it a string based on how much you want to move it forward.  "hhmmmm" would move it forward 2 hours and 4 minutes.  Sending an "s" just sets the seconds back to zero, as per Jeff's idea.
The display is a preallocated string initialized with "hh:mm:ssnm".  Each time there is an update to the time, each byte is set based on the value/10 or value%10, with '0' added to it to create the correct ASCII code.  I thought this was most clever.

I am planning on adding features to it, namely the ability to set it from the Arduino without a computer and USB cable.  I have 4 I/O pins left, so it will be possible.  Two of them also happen to be PWM pins, which would make running something like a buzzer off them really easy, so there may be alarms in its future.

And for your pained enjoyment, the code in its entirity.  The 1Hz wave is connected to pin 3, Enable to 2, DB4-DB7 to pins 7-10, R/W to 11, and RS to 12.  In the picture above, it is shown running off of a random 12V wall wart I had rolling around on my floor.  Makes more sense than having a clock run off a computer all day.

Code for Arduino Clock:
#include <LCD4Bit.h>

LCD4Bit lcd = LCD4Bit(2);
volatile boolean updated = false; // Used to indicate a new second
char time[] = "hh:mm:ssnm"; // Each byte will be set to display the time correctly
int seconds = 0;
int minutes = 0;
int hours = 0;
int ampm = 0; // am = 0, pm = 1


void setup()
{
  Serial.begin(9600); // Needed for setting the time over USB
  lcd.init();
  attachInterrupt(1, blink, RISING);
}

void loop()
{
  if (Serial.available()) {  //set the time using the serial monitor
    int inputchar = Serial.read();
    switch (inputchar) {
      case 'm':  // Increment minutes
        minutes++;
        break;
      case 'M':  // Added too many minutes, subtract one
        minutes--;
        break;
      case 'h':
        hours++;
        break;
      case 'H':
        hours--;
        break;
      case 's':
        seconds = 0;
        break;
    }
  }
  if (updated) { // On a new second, add 1 to seconds, and do all the carrying. Update display.
    seconds = (seconds + 1) % 60;  //update the time when the crystal fires
    if (seconds == 0) minutes = (minutes + 1) % 60;
    if (seconds == 0 && minutes == 0) hours = (hours + 1) % 12;
    if (seconds == 0 && minutes == 0 && hours == 0) ampm = 1 - ampm;    time[0] = (hours / 10)+'0';
    // Update the string "time"
    time[1] = (hours % 10)+'0';
    if (hours == 0){  // handle the special case of 12, represented as 0
      time[0] = '1';
      time[1] = '2';
    }
    time[3] = (minutes / 10)+'0';
    time[4] = (minutes % 10)+'0';
    time[6] = (seconds / 10)+'0';
    time[7] = (seconds % 10)+'0';
    if (ampm) time[8] = 'p' ;
    else time[8] = 'a';
    time[9] = 'm';
 
    lcd.clear();  // update the LCD
    lcd.printIn("   ");
    lcd.printIn(time);
    updated = false; // Reset the new second flag
  }
}

void blink()  // interupt handler. Name left over from the AttachInterupt example.
{
  updated = true;
}


Now a Card Carrying Member of the ARRL

So I just mailed off my membership form for the ARRL.  The ARRL is the Amature Radio Relay League, which is pretty much the Ham radio equivalent of the NRA for gun users.  The ARRL communicates with the government to protect our rights as Ham operators.

They also have a lot of other perks like an overseas QSL card mailing service, email forwarding from KI6RLA@ARRL.org to my own email address, and a monthly magazine subscription.

Down the rabbit hole I go.  Want to come along?

Wednesday, July 2, 2008

Stupid C Book

This is a direct quote from the textbook for my C programming class:
Most programming languages have three strateges for inter-function communication: pass by value, pass by reference, and return.  The C language, unfortunately, uses only the first and last strategies; there is no pass by reference in C. (Computer Science: A Structured Programming Approach Using C 3rd ed. pg. 176)
I just do not know where these people get off.  If passing a pointer isn't pass by reference, I don't quite know what is.  This is all completely insane!  Thank god I'm heading off for Portola in 30 hours.

All I'm going to say is that this programming class is a very good lesson in how to keep my mouth shut.

Initial Work With the DS1306

In my second round of free samples from chip makers, I requested a pair of DS1306+ and DS28CZ04G-4+.

The DS28CZ04G-4+ is a 4kB EEPROM, among other things. I had already requested larger EEPROMs from Atmel, but they haven't been responding to my requests at all, so forget them. Unfortunately, this was a complete bust, based solely on package size (The IC, not mine).

On the other hand, the DS1306+ managed to get out the starting gates without falling flat on its face, due to it being in the beloved DIP package, which fits perfectly in breadboards. It is a real time clock chip, which means it just keeps time. All you need to give it is the standard 32768 Hz crystal, usually found in watches, and it'll sit there and keep time for you. It has a ton of neat features you can access through the serial interface, like it being smart enough to know how many days in each month, and which years are leap years, etc. I just haven't gotten there yet, and have only played with its most basic feature, the 1 Hz square wave.

It's simple: Every second, one of the pins is high, then low, then high on the next second. I then hooked this pin up to one of the interupt pins on the Arduino, so every time the square wave raised from low to high, it triggered an interupt in the Arduino, and called a function. In the example on the Arduino interupt page, it just turns an LED on and off, which was all I really needed for my first trial.


So I wire everything up, load the sketch, and... it kind of works. Every second, the LED flickers and ends up either on or off, almost completely at random, but every second. So we have a bouncing problem. Instead of the Arduino seeing a clean rise (like this: .......''''''''), it sees multiple rises, which triggers the interrupt multiple times (like this: .......'.'..''.'.''''''''). I tried playing around with the standard debouncing software tricks, but had little success.

Luckily, I then happened on another writeup about the DS1306, and all I needed was that first picture at the top. See that brown resistor on the right side? That's what I was missing. The DS1306 doesn't actually generate a 1Hz square wave. It just drains the pin every second. With a pull-up resistor holding the pin normally at HIGH, then the DS1306 pulls it down to LOW when it should be. What I was doing before was letting it float half the time, then the chip pulled it low half the time, which is a big no-no in digital logic. I'm surprised the Arduino didn't see it more like this: ...........''.''.'.'.'.''.............'.'.'.'''.'.''...'.''.............


So long story short, I added a 10k ohm resistor between Vcc1 and 1Hz, and the bouncing problem disappeared completely. I then added a counter to the interrupt function and monitored it through the USB serial port, and the chip kept time to within a second after 30 minutes, which shows there is at least nothing seriously wrong with it.

And the moral of the story is: Data sheets may be 20, 30, or even 300 pages long, but the amount of information they hold warrant that. If you're having problems, before falling into chaotic engineer-solving-problem mode, reread the data sheet one more time.

Tuesday, July 1, 2008

HD44780 Command Codes

Never mind the fact that I've been spending the vast majority of evenings geeking out with Jeff Glass, we've made quite a bit of progress on the Arduino.

After figuring out the pinout on my LCD display on my last post, we then hooked it up to the Arduino, and it worked!  Unfortunatly, it only displayed text on one line, which is no good.  Reading through the data sheet for the HD44780, we realized that there is a ton of different codes you can send the LCD to do useful things (including turning on the second line).  The command code table is on page 24.

The table really isn't very clear, so we spent a lot of time stumbling around, only getting complete garbage on the screen.  Translating from the table's binary representation to decimal values was a pain in the ass, so we first off broke out the good old binconst.h trick to allow us to place binary constants straight in the code.  After futzing around for a while, we think we've figured out most of the command codes and what they do:
  • 12 - Turn off the cursor
  • 24 - scroll the text left, this is good because it's done by the controller and has much less flicker than doing it by lcd.clear(), lcd.printIn(string+1);
  • 28 - scroll the text right
  • 56 - turn on second line.  It divides the 80 byte buffer between the two lines, so to write on the second line, you need to make sure the printIn(string) string is 40 byes long for the first line, then it will print at the beginning of the second line.  How I'm handling this is I have a 40 char buffer for the first line, which I then copy text into, printIn it, then can print whatever I want, and will go onto the second line.
So those were the command codes we found useful.  We didn't bother to figure out how to undo them, since it was just easier to call lcd.init() again.  This is unlikely, but anyone have any of the other command codes out of that ridiculously cryptic table on the data sheet they found useful?