It seems like an obvious point, but it’s sometimes handy to know what your code is up to.
If you normally write code for desktop systems – and especially if you’re hacking out something that works on the command line – then it’s easy. Just pepper your program with print statements to show the state of play. (Then try to remember to delete or comment them all out when the code is ‘finished’.)
That’s not so easy when you’re hacking away on a microcontroller. There’s no handy screen to display your variable values. So what are your options for monitoring and debugging?
I faced this dilemma on a project I’ve been tinkering with for the past few days. I’ve been thinking about a robot project that’s been simmering away on the back burner for some time now. In particular, I need a way to pass messages between a number of AVR (probably) microcontrollers and a master Raspberry Pi computer. I’ll deal with this in more detail some other time, but the options basically are:
- Serial (ie, UART-based) comms.
- Shared memory (ie, a piece of RAM shared by all devices).
I’m familiar and comfortable with serial comms between AVRs, Arduino-alikes (including the Teensy), Raspberry Pi and BeagleBone. I’m fairly okay with I2C, too. When it comes to SPI, though, I’ve only ever used readymade libraries for the Arduino platform. And as for using RAM chips, I’d never tried that at all.
And it just so happens that I have a few 23LCV512 serial RAM chips hanging about. These use an SPI interface to pipe data in and out, so I could use them to learn about using RAM and SPI at the same time.
So I got out my ATMEGA328P prototyping board, plugged the RAM chip into the breadboard and opened a new project in Atmel Studio. Then I got to thinking how I was going to do this.
The obvious thing is to write some values to RAM and then try reading them back again. Which is fine, except how would I know if it had worked?
In the end, I realised I had four options – and I used three of them.
First up, what do you do with a system that doesn’t have a handy screen? Put a screen on it.
In my parts bins I had an I2C-driven 20×4 LCD display. I dusted off my AVR I2C class and, after a bit of head scratching and a couple of false starts, soon had the screen displaying my variables.
But there was a limitation to that. I had variables representing a one-byte value and a two-byte address and used them to send data to the RAM chip. And I could print these values on the screen. But this just told me what was in my variables, not what the RAM chip was actually experiencing.
And the values coming back from the RAM in the read operation were wrong. I should have got a sequence of values from 0x10 to 0x1F but instead the screen was showing 0xFF for all the memory locations.
So, on to the second debugging method – using an actual debugger. I’ve written before about using debugWire on AVR chips, so I took my own advice. It was particularly useful to be able to see the state of the ATMEGA328P’s registers. I noticed, for example, that the MSTR bit in the SPCR register – which puts the AVR into SPI master mode – was becoming unset at one point. That shouldn’t happen and I was able to track down the cause (a stupid mistake on my part – let’s leave it at that.
I also found a couple of other small bugs thanks to the way Atmel Studio displays registers in debugging mode, so it was a useful exercise – but those reads were still coming back with 0xFF.
I really needed to know what was happening between the AVR and the RAM chip. Did I need a better pullup on the MISO line, for example? I was using the ATMEGA’s internal pullup, which is probably weak.
On to the third debugging option – the oscilloscope. When I was in the process of choosing a scope, I was torn between a two-channel model with high bandwidth and the Rigol DS1054Z with four channels but lower bandwidth. I nearly got the two-channel product on the assumption that it was unlikely I’d need more than that but one can always use more bandwidth.
I’m glad I didn’t. I hooked up all four of the Rigol’s channels to the MOSI (Master Out, Slave In), MISO (Master In, Slave Out), SS (Slave Select, aka Chip Select, CS) and CLK (clock, aka SCK) lines and it was invaluable to be able to see all of them on the screen together.
One thing it confirmed was that the SPI mode was working as I thought it should. A careful reading of the RAM chip’s data sheet showed that the chip latched incoming values on the clock’s rising edge. That meant the SPI mode had to be either 0 or 3.
In mode 0, with the polarity (CPOL) and phase (CPHA) both 0, the rising edge (where data is sampled) is assumed to be the leading edge of the clock while the falling edge (where data is prepared) is assumed to be the trailing edge. The leading and trailing edges are reversed in mode 3. I tried both, watching what happened on the scope. Mode 0 won.
In the above screengrab, look at the MOSI (yellow) and CLK (light blue) traces. You can see that MOSI goes high when the clock falls. This is the data being prepared. It’s then still high when the clock rises again, which is the point where the bit gets latched by the RAM.
The response was still wrong, though. But at least now, when I made a change to the code I could see the physical effect on the scope.
With the help of these methods, little by little I was able to chip away at the bugs until, finally, it worked. This is what a successful read looks like (click on the image to see it bigger).You can see how CS (purple) goes low at the start of the operation to enable the slave device and goes high again at the finish. The CLK line shows some slightly irregular pulses – these are actually gaps between the bytes being sent by the master device (the AVR).
The MOSI (yellow) line displays four bytes being sent. The first byte (0x03) is the ‘command’ to the RAM chip telling it this is a read operation. That’s followed by the upper (MSB) byte of the address – all zeroes in this case. There the line goes briefly high, but that’s just a pause between bytes and coincides with the clock being low for slightly longer than normal. Because the clock doesn’t go high during this period, it’s not interpreted as data by the RAM chip.
The next byte out is the lower (LSB) byte of the address – 01000110, or 0x46.
Finally, the AVR pushes out 0x00. The peculiarity of the SPI bus is that it sends and receives at the same time. To get a value from the slave device you often have to send it something – anything. And you can see that the darker blue line has data coming through at the same time as we’re clocking out that last byte – 01010110 (0x56). This is the correct value because my test program was saving values that were 0x10 larger than the address, so address 0x46 should indeed have had 0x56 in it.
So the three debugging methods I used for this screenless microcontroller were:
- Adding a screen.
- A debugger.
- An oscilloscope.
And each was useful in different ways.
Oh, and the fourth? That would have been a logic analyser. I’ve just bought one of those cheap eBay specials but haven’t yet had a chance to mess with it. That’ll be for another time.