It can be very difficult to convey to students sometimes just how much effort can go into diagnosing a circuit that isn’t working, and how much tenacity it takes. There are many resources that can be used to try to overcome problems. I figured it might be a good idea to write about my experiences troubleshooting a circuit that caused me several hours of headaches before I fixed the issue.
The project is “simple” (as are all things!), just to establish SPI communication between a DS1305 Real Time Clock and an AVR ATmega328P microcontroller, using C (and the Arduino IDE). My projects always exist in stages. The big picture is that eventually I will synch this up to the atomic clock time using a WWVB module, but it is best not to get bogged down in the big picture. The first step in working on a project is to break it up into digestible pieces. All I wanted to do at first was read hours / minutes / seconds from the RTC and see that the time increments as I would expect. I will build on to the project incrementally once I establish this baseline.
I’ve worked with real time clocks before, but this time I wanted to write my own software instead of using a library. I reasoned that it couldn’t be that difficult (famous last words!) considering I am familiar with SPI.
The first step was to read the datasheet for the DS1305 chip as well as the datasheet for the microcontroller. I need to know how to wire up the chip, as well as which pins need to interface between the two.
Considerations to make with the DS1305 chip:
- Backup power source (you can use a rechargeable battery or super cap, or a non-rechargeable lithium coin cell), this effects the choice of connections on Vcc1, Vccif, Vcc2, and Vbat (there are a lot of power pins on this chip!) — I will be using a non-rechargeable coin cell battery
- Serial mode (you can use 3-wire or SPI) which effects how you connect SERMODE, and SDO (notably) — I will be using SPI mode
- Use of interrupts (you can generate interrupts on alarm conditions to turn this into an alarm clock) which effects how you connect INT0 and INT1 — I will not be using any interrupts
Here is the pinout diagram from the data sheet, and also my schematic diagram including connections to the ATmega328P microcontroller.
I have to note that I was unclear at first about what exactly I should be doing with all of the power pins on the DS1305. I didn’t have a coin cell holder, and the data sheet seemed pretty clear that the chip wouldn’t function without a backup supply of some sort. I solved the issue by connecting the Vbat pin to 3 V from my DC power supply. This was always in the back of my mind as a possible source of problems in case anything didn’t work.
Now that the hardware was connected, I needed to determine how to configure an SPI connection in software, and then either read data from or write data to (or both) the DS1305.
Resources I used for determining how to configure SPI in software:
- Microcontrollers lab manual — this is always my first step to checking what to configure for each bit in the SPI setup and configuration registers
- ATmega328P datasheet — this is always used as a backup in case there is a typo in the lab manual or in case I need further information about something that didn’t make it into the lab manual
- DS1305 datasheet — I needed to know what the chip needs in order to function properly
Because there are two serial modes that work with the DS1305, I made sure that everything was connected properly to use SPI. (SERMODE = Vcc, CE = SS, SCLK = SCK, SDI = MOSI, SDO = MISO) One notable thing is that the CE is active high, which is opposite most devices. This means that I needed to set SS on the ATmega328P low in order to deassert the chip, and only drive it high when I needed to read/write.
To configure the SPI you need to use a couple different registers: SPCR and SPSR.
SPCR is the big kahuna here. It contains all of the information we need to make the circuit work. SPIE, SPE, DORD, MSTR, CPOL, CHPA, SPR1 and SPR2 are the bits in this register. Let’s discuss each one:
- SPIE — this bit enables SPI interrupts, which I (almost) never use in master mode, so I left this bit as 0
- SPE — this bit enables SPI, which I obviously want in this case, so it is written 1
- DORD — the datasheet for the DS1305 is pretty clear that it expects MSB first, which corresponds to a 0
- MSTR — I want the microcontroller to be the master, so this bit gets written 1
- CPOL / CHPA — here is where you need to start reading carefully through the DS1305 datasheet. It discusses that it is able to use both values of CPOL, which is the level that the clock signal is held to when it is not in use (see figure below). I decided to keep CPOL as 0 (which means the clock is written low when no data is being sent over the SPI interface), but then I needed to figure out what to do with CHPA. At first I just shrugged my shoulders and figured 0 would be a good start.
- SPR1/2 — these two bits control the prescaler of the SPI clock. At first I kept them both 0, which corresponds to fCLK / 4 (16 MHz / 4 = 4 MHz), which again was just a starting point.
SPSR has three bits to configure, each of which I kept at 0.
Reading the datasheet for the DS1305, I furthermore learned that I needed to configure the control register on the DS1305, in order to enable the clock and also to enable writes to the chip. This means I needed an understanding of write modes.
One of the write modes is SPI single-byte write. By looking at the timing diagram (below) you can see that the clock needs to tick 16 times, each time sending a byte of information (address followed by data to write at that address). I had no idea how to do this, so I decided not to do it this way. The SPI on the 8-bit AVR devices works in chunks of 8 bits. Sometimes the best way to figure out which way to do things is to realize you have no idea how to do it one way, therefore you will decide to do it the other way.
So I’m left with the other option, which is SPI multiple-byte burst transfer. Looking at the timing diagram, it appears that CE must be held high continuously. You first send the address bit, then you send the data bit. Okay, this one I can figure out how to do. It should be as simple as:
- Assert CE (write a 1 to the SS pin on the microcontroller)
- Write the address to SPDR (SPDR = 0x8F, or whatever the address is)
- Wait for the transfer to complete (wait for a 1 bit in SPIF in SPSR)
- Maybe wait a couple microseconds?
- Write the data to SPDR (SPDR = 0x00, or whatever the data needs to be)
- Wait for the transfer to complete
- Deassert CE (write a 0 to the SS pin on the microcontroller)
Because I like to bite off more than I can chew, I figured why not also write code that can read data from the DS1305. This seems to be as simple as:
- Assert CE (write a 1 to the SS pin on the microcontroller)
- Write the address to SPDR (SPDR = 0x00, or whatever the address is)
- Wait for the transfer to complete (wait for a 1 bit in SPIF in SPSR)
- Save the data from SPDR to a variable, which is returned from the function
- Deassert CE (write a 0 to the SS pin on the microcontroller)
Here is the code that I used for writing to the SPI:
unsigned char writeSPI(unsigned char addr, unsigned char databyte) {
PORTB |= 0x04;
SPDR = addr;
while (!(SPSR & (1 << SPIF)));
unsigned char temp = SPDR;
_delay_us(10);
SPDR = databyte;
while (!(SPSR & (1 << SPIF)));
PORTB &= 0xFB;
return SPDR;
}
And here is the code that I used for reading fro the SPI:
unsigned char readSPI(unsigned char addr) {
PORTB |= 0x04;
SPDR = addr;
while (!(SPSR & (1 << SPIF)));
PORTB &= 0xFB;
return SPDR;
}
Here was my initial configuration for the SPI:
void setup() {
// configure the SPI system
cli();
DDRB = 0x2C;
PORTB &= 0xFB;
SPCR = 0x50;
SPSR = 0x00;
sei();
// configure the RTC to be write enabled
unsigned char temp;
temp = writeSPI(0x8F, 0x00);
}
I used the serial monitor for debugging purposes, and also my oscilloscope, and got to work. The only data I got on the SDO pin was 0. The serial monitor read continuous 0 values. My oscilloscope showed a 0 (which was somewhat misleading, as the SDO pin was actually tri-stated, but the inner Earth connection on the ‘scope made it pull low).
So all I knew at this point was that something was not working. I didn’t know if it was hardware or software or both. Maybe it was a bad chip. At this point there was very little to go from. I didn’t know if my write operations were working, but not my read operations, or if nothing was working. It was too hard to say.
Based on the fact that I wasn’t getting any data from the SDO pin, I thought maybe a pull-up was needed, but that seemed unlikely considering that it wasn’t called for or noted in the DS1305 datasheet. I tried a pull-up, and then a pull-down, of 10k and 100k, and no success was noted. I Googled a million permutations of “SDO pin AVR DS1305” and found two forum posts about this issue, none solved, one of which mentioned using a 1k series resistor between SDO and MISO. That did not help either.
In the back of my mind was the issue with the power pins. After comparing to the typical operating circuit in the DS1305 datasheet, I figured that I was good to go as long as Vbat was connected to 3 V (it was).
I read more information about the DS1305. At this point I figured I was not initializing SPI correctly. Call it a gut instinct, but that’s where I was going to start now that the use of resistors didn’t seem to solve anything.
A forum post gave me some starting points. (2024 update: the forum post is gone, so I took down the link that was initially posted here.) Somebody else was having the same problem as me! But the problem wasn’t solved! Frustrating! But some nuggets of greatness came from reading this. First I noticed that I needed CHPA = 1. Whoops! 0 was just a starting point. After reading this application note from Maxim IC (now Analog), I realized that you can have CPOL = 0 CHPA = 1 or CPOL = 1 CPHA = 1. Either way, the clock phase needs to be 1. Great! I found a problem! I re-uploaded the code, still not working.
Now I took out my oscilloscope probes and started taking a good look at my input signals. My ‘scope is a 100 MHz model but I noticed the clock signal was looking a little off. Could be the resolution of the ‘scope but this made me think that I hadn’t actually read the datasheet to determine the operating frequency of the serial clock that the chip can handle. (The forum post on the AVR freaks website was probably using either an 8 MHz or 1 MHz internal clock; I was using a 16 MHz external crystal on my microcontroller.) Sure enough, the datasheet notes that the max frequency is 2 MHz and here I was blasting it with 4 MHz! So I decided to use the prescaler of 128, meaning now my SPCR = 0x57.
I found a second problem! I re-uploaded the code. It still didn’t work. This, by the way, represented about 3 hours of tinkering. I was frustrated. I walked away from the project, went to sleep, didn’t touch it the next day, got another good night’s rest, and decided to tackle it again the next morning after a cup of coffee.
Sadly, I had no sudden flashes of insight onto my problem. Not even something to try. Nothing. It was just going to take more hammering away, more Google searches, more reading of the datasheet.
At this point, I was reasonably certain that I configured SPI properly, and that the inputs were going to the DS1305 correctly. I needed to spend some time with my oscilloscope on the input pins. I was mostly interested in the CE and SCLK pins, and I wanted to compare them to the timing diagram.
I also probed the SDI pin and noticed that the address and data bytes were transmitting as expected.
Then I took a look at my read process.
WAIT JUST A MINUTE! Notice that the CE and CLK signals remain asserted for TWO CYCLES of data in the datasheet! My oscilloscope is only showing ONE CYCLE of data! No wonder I’m not getting any output on SDO! The CE is deasserted and the output pin SDO is tri-stated because of the CE value! So clearly I need to send another byte of data after sending the address, just to get it to send data after the second byte goes through. I updated my read function in software.
unsigned char readSPI(unsigned char addr) {
PORTB |= 0x04;
SPDR = addr;
while (!(SPSR & (1 << SPIF)));
SPDR = 0;
while (!(SPSR & (1 << SPIF)));
PORTB &= 0xFB;
return SPDR;
}
The only difference is now I am sending a 0 to SPDR, waiting for that data to transmit, and then returning SPDR. It totally worked! Here’s a look at my new read timing on the oscilloscope.
Now I was finally able to read data from the DS1305. The seconds started ticking away at a rate of 1 Hz (as it should, with the 32.768 kHz crystal on the DS1305).
I know that this is a really long post, and hopefully by reading through you can see that it takes an awful lot of patience, reading, re-reading, searching, head-scratching, and debugging to find hidden issues in code. Don’t overlook any part of the datasheet. Even if and especially when datasheets look terrifying and confusing, you should read them. A few years ago reading datasheets used to make me squirm. But the more you read them and try things out, the more things start to make sense. You can only build your understanding by tinkering and trying different things. Don’t be afraid to try different approaches to solving a problem. Many times, there isn’t a solution on the Internet. Or if there is, it’s written by people whose knowledge far outstrips ours and is written in a way that can be very difficult to understand for those who are just tinkering around or learning.
Exhaust all possible solutions in one avenue before turning to another avenue. I had pretty much excluded all hardware issues before I looked into software problems. I exhausted all issues with the SPI setup before looking into my read / write operations.
Use all of the diagnostic tools at your disposal. I used a multimeter, logic probe, and an oscilloscope (although I only mentioned the ‘scope because it was the most pertinent). Sometimes an issue is just “I didn’t connect ground on the chip” and a logic probe will find it. Sometimes the issue is due to timing (as my issue was), and only an oscilloscope will do.
But whatever you do, keep trying and don’t give up until you solve the problem! Once you do, it will be the best feeling in the world.