Wednesday, 13 August 2014

Me, an Arduino and a DS3231 Real Time Clock


Check out the video above for a demonstration!

The grand project I am working on is a clock radio, and what good is it if it cannot accurately track time? It was tempting to try to set up the microcontroller to accurately track the time, but it would likely drift out fairly quickly. I decided that I would use an RTC (real time clock) module to keep track of the time for me. I looked around a little on google to see what others were using, and I looked around a little on eBay to see what was cheaply available and I settled on the DS3231. A few reasons for this:
  • Extremely accurate
  • Battery backup
  • I2C
  • Works with 5V
  • You can even get a temperature reading from it!
I purchased two units from eBay for $5.62US. Always buy at least two. If you wreck one, or one is broken then you don't need to wait - especially if ordering from China, you may need to wait 2-3 weeks to get your replacement!

They came in this nice static bag. On the bag it says "Raspberry Pi", which it seems like they were targeting this item to be sold to people who wanted to use it with the Pi, but it works just fine with any I2C capable device (I wonder if people looking for Arduino modules don't buy it because of the Raspberry Pi labeling?)





When you're looking around on the internet to buy these, it is important to remember that the DS3231 is the chip mounted on the board! They are not all created equal. This version only uses 4 pins - VCC, GND, SCL & SDA. It does deal with the battery on the board for you, which is nice, but you don't get a pin for reset, the 32KHz has an output on the chip as well as a pin for triggering an interrupt or sending out an adjustable square wave. If you want these features, you might want to see what other options are available for the DS3231.

It's sitting on a nice female header, which will make it easy to mount onto my perf board later. It came with that little battery - I doubt I'll ever replace it. It's everything I need, and more. I just wanted a 24 hour clock, this chip can track day, month, year and store 2 alarms.

Let's move onto the datasheet for this puppy, which can be found here: DS3231.pdf

It's only 20 pages, and a quick skim couldn't hurt. It picks up on page 11 when you get to see how the registers are formatted. It's all in BCD (binary coded decimal), so you need to work on the data before you send it out or display it - so '12' would be shown as '0001 0010' (I might do a video on bit manipulation, BCD and binary conversions some day). I'll attach some code to this blog entry, I feel it might be easier to see what's going on by looking at the code, rather than me try to explain it. The I2C is a little weird feeling on this, but I think I have a pretty good understanding after playing around with it for a little while.
I can try to save you some trouble with a mistake I made. They give an example of how to receive information from the data sheet - And no where in the data sheet do they tell you the address in plain English...
This might be my fault, but under slave address I was thinking a full byte had to be sent, but the 'slave address' was only 7 bits, so I put a '0' to the far right which I thought completed the 8 bits. So when I tried to address it, I was trying to talk to '0xD0', but for some reason you just read the 7 bits and imagine a zero as the MSB (most significant bit), which means the address was actually 0x68. Maybe if I spent more time playing with I2C devices this would have been more obvious? Who knows. I did learn something interesting from this: When I was trying to read data with the first address, I kept ending up with the retrieved bytes being 0xFF, which is all 1's in binary land, so for now on, if I'm working with I2C and I end up with nothing but 1's coming in the bytes, I'll assume either the address is wrong or the device is not connected - this could save troubleshooting time someday.
Moving along, I'm only really interested in the first 3 registers which handle the seconds, minutes and hours. I may yet do something with the temperature for fun.
You can look in the code to see the bit manipulation I used to extract the human readable numbers, it's quite easy once you get over the anxiety of dealing with thinking in binary.

One last hardware note: I'm holding up the lines to the 5V line with 4.7K resistors, with no issues.

Here's a picture of the Arduino board with the DS3231 module above. It ties on to the bus on the breadboard to the right, which also has the 4.7K resistors on it. The entire project is being powered by USB at this time.








This is the whole project with my radio portion (scroll down on the blog to learn more) TEA5767, the LM386, speaker, and of course, the DS3231.










For your viewing pleasure, another horrible paintbrush schematic! If you don't want the radio portion, you can ignore everything to the right of the DS3231 module.
The code includes the functions for the radio section. If you just wanted to use the getTime and initializeClock functions, you could cut and paste them - be sure to include the Wire.h header. Check back in the future to see this finished, the code will be more complete and I will have hopefully settled on how to have the user interact with it all.

#include <Wire.h>

unsigned char frequencyH;
unsigned char frequencyL;

unsigned int frequencyB;
double frequency;
double tunerFrequency;

unsigned int mainLoopCounter;

void setup()
{
  Wire.begin();
  frequency = 95.9; //A fancier approach would be to store this in EEPROM and retreive last channel ;)
  Serial.begin(9600);
  setFrequency();
  initializeClock(); //set the time in the initialize clock function and uncomment this line, upload,
                     //then comment this line out. It will retain your original time and not overwrite.
                     //A proper setting method will be incorporated later!
 
  Wire.beginTransmission (0x68); //if the clock isn't on register 0x00 at startup, it won't work properly
  Wire.write(0x00);
  Wire.endTransmission();
}

void loop()
{
  int reading = analogRead(0);
  //Serial.println(reading); //Shows the adc position - setting it to 512 will point "up" so you can attach a knob
  if (reading <= 410 || reading >= 614) checkTuner(); //only check tuning if tuning knob leaves rest area
 
  mainLoopCounter ++; //I know this is a little lame, won't be part of final
  if (mainLoopCounter == 2000)
  {
    getTime();
    Serial.println(frequency);
    mainLoopCounter = 0;
  }
}

void checkTuner()
{
  int currentReading = analogRead(0);
  if (currentReading <= 205) frequency = frequency--; //Tune back by 1MHz
  else if (currentReading >= 206 && currentReading <= 410) frequency = frequency - 0.1; //Tune back decimal
  else if (currentReading >= 614 && currentReading <= 819) frequency = frequency + 0.1; //Tune forward decimal
  else if (currentReading >= 820) frequency = frequency ++; //Tune forward by 1MHz
  else frequency = frequency; //If something unexpected happens, this will buffer it out
  if (frequency < 88.0) frequency = 88.0; //make sure it doesn't tune too low *Band limits can be adjusted
  if (frequency > 108.0) frequency = 108.0; //make sure it doesn't tune too high
  delay(300);// this delay will only occur if the main loop makes it into this function
  Serial.println(frequency);//only prints frequency while being tuned
  setFrequency();
}

void setFrequency()
{
  tunerFrequency = ((int)(frequency * 10)) / 10.0; //fun use of a cast!
  frequencyB = 4 * (tunerFrequency * 1000000 + 225000) / 32768;
  frequencyH = frequencyB >> 8;
  frequencyL = frequencyB;
  Wire.beginTransmission(0x60);
  Wire.write(frequencyH);
  Wire.write(frequencyL);
  Wire.write(0x1A);//0001 1010 -highside injection, forced mono, "right channel" muted, *PINOUT AND 3RD BYTE HAVE CHANNELS SWAPPED
  Wire.write(0x10);//Set for US and Europe band
  Wire.write(0x00);
  Wire.endTransmission();
}

void getTime()
{
  Wire.requestFrom(0x68,19,true); //its polling all 19 registers so it will be back to register 0x00 on next read.
  byte second = (Wire.read());
  byte minute = (Wire.read());
  byte hour = (Wire.read());
 
  //converting from BCD to decimal
  Serial.print(hour >> 4 & 0x03);
  Serial.print(hour & 0x0F);
  Serial.print(":");
  Serial.print(minute >> 4);
  Serial.print(minute & 0x0F);
  Serial.print(":");
  Serial.print(second >> 4);
  Serial.println(second & 0x0F);
}

void initializeClock()
{
   Wire.beginTransmission(0x68);
   Wire.write(0x00); //start at address 0x00
   Wire.write(0x00); //seconds - enter numbers how you would normally see them
   Wire.write(0x44); //minutes - these numbers would set the time to 22:44:00
   Wire.write(0x22); //hours (24h)
  //NOTICE: I only had to specify register once, it can increment the register on its own.
   Wire.endTransmission(); 
}


That's all I have to say about this for right now. Stay tuned to see what happens next. Hopefully this will help someone else working on something similar.

Good luck with your project!

No comments:

Post a Comment