Introduction: Intro to Microcontroller Debugging (and a Pomodoro Timer)
Hello Hack-A-Day and Dangerous Prototypes readers! Please vote for me in the Sparkfun uC Contest! (If you vote for me you can still vote for other projects as well) The vote button is just up and left of here... there you go... closer... closer... I know you can do it...
1.) I do, in fact, own an Arduino and despite this being a microcontroller project on Instructables, an Arduino is NOT a part of this guide. An Arduino has it's specific uses and fills a niche in my toolkit very well but Arduino is usually not my first choice when deciding which platform I'll prototype my latest idea with. One of the best parts of the Arduino platform is that it is streamlined to the point that anyone can use it without being an engineer. However, someone serious about the code they are writing for their project, will before long, need an essential feature: in-circuit debugging. We'll go over why ICD is so great and discuss how to use it as we build this simple project.
2.) What the heck is a Pomodoro timer, you ask? The Pomodoro technique is a means of time management created by Francesco Cirillo. You can find lots more information here: http://www.pomodorotechnique.com/ Personally, I love the Pomodoro technique but didn't want to run yet another application on my computer. Thus, I needed a simple timer. If you don't need or want a Pomodoro timer, the end result of this Instructable isn't as important as the means of getting to it and learning how to debug. What I really want to teach is some simple debugging concepts your average Arduino user may not know they are missing out on.
3.) This project is based around the Texas Instruments LaunchPad development board, an MSP430G2211 and five LEDs. If you don't have a Launchpad and have any interest in embedded programming at all, please just order one now. You can thank me later.
Take a look at the final step for the components needed for the pomodoro timer, if you're interested in making one.
4.) IAR Embedded Workbench Kickstart for MSP430 is the development environment we'll be using. Download it from here: http://www.ti.com/iarkickstart This is unfortunately a Windows-only IDE, but it runs in various virtual machines on both Linux and OS X. For this Instructable, I'm using Parallels on OS X. If there is sufficient interest, I'll write a guide for MSP430 development using FOSS tools on OS X.
Step 1: Putting the Prototype Board Together
Remove the TXD, RXD, and P1.6 jumpers from the board. Put the microcontroller in the Launchpad if you haven't already. The groove in the microcontroller goes towards the USB connector.
LEDs don't work if put in backwards so pay attention to their polarity. Here is a quick primer if needed: http://www.sparkfun.com/tutorials/222. Thanks Sparkfun!
For all five LEDs, bend the cathode straight out. (That's the short leg). Put the LEDs, 100ohm resistor, and paperclip together like they are in the picture. I'd prefer a bit solder to the paperclip, but we're keeping things as simple as possible.
The LEDs, one each, are in ports P1.1 – P1.5. The paperclip prototyping method seen below works in lieu of solder. The 100 ohm resister completes the LED's path to GND.
My piezo speaker was a lucky find as it already had a .1” pitch header on it. It that is the case, you can remove the P1.6 jumper and attach the speaker directly to the board. Otherwise, you need to make a connection from P1.6 through the speaker to GND.
For those that want to make this a stand-alone device, the final schematic is included in this guide on the last page.
Step 2: Setting Up the Project
Launch IAR Embedded Workbench.
From the menu, select “Project” → “Create New Project...”
Make sure the Toolchain is “MSP430” and select “main” from under the “C” option. (See image 1)
Save the project as “pomodoro” when prompted.
Go to the “Project” menu and select “Options”. Under “General Options” make sure the target is “MSP430G2211”. Under “Debugger” the “Driver” option should be “FET Debugger”. All other options should remain at their defaults. Close the options dialog. (images 2 & 3)
A template main.c should be open. We're going to replace it with our own code from below:
#include "msp430g2211.h" #define LED4 BIT1 #define LED3 BIT2 #define LED2 BIT3 #define LED1 BIT4 #define LED0 BIT5 #define BUZZER BIT6 void main( void ) { // Stop watchdog timer to prevent time out reset WDTCTL = WDTPW + WDTHOLD; // Set DCO to 1MHz factory calibration value BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; //all LED pins and buzzer as output P1DIR |= (LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER); while(1) { //loop forever //all LED pins and buzzer on P1OUT |= (LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER); //all LEDs and buzzer off P1OUT &= ~(LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER); } } |
From the menu, select “Project” → “Compile”. If prompted to save your workspace, you can give it the same name as the project, i.e. “pomodoro”.
As long as there were no problems compiling your project, you should be able to upload to the board. To do this select from the menu “Project” → “Download and Debug...”
Step 3: Debugging: First Steps
So why isn't anything happening? (You should have a green line of code as in image 1).
Here's what's going on. The compiled code was uploaded to the microcontroller and the microcontroller has started to execute that code. However, it has paused at the first instruction. The green arrow and green line of code indicate the execution is paused at line 13.
Press the “Step Into” button () four times and watch as the code execution advances. It should have stopped on line 20, which tells the microcontroller to turn on all the LEDs. Why aren't the LEDs on? While using the debugger, the green arrow and line of code indicates which line of code is about to be executed. It has not happened it. Press “Step Into” () once more .
Now is a good time to check your LEDs. All five should be illuminated. Make sure all your connections are good, that the LEDs are lit, and press “Step Into” () again. Obviously, all the LEDs should be off now.
Click the “Go” () button. The microcontroller is now running is if you were not debugging it. Instructions are flying by, one million per second, and the the LEDs are toggling half that fast. Thus, they look half as bright as they did when halted in the debug session.
To verify the dimming effect, hit the “Break” () button. There is a fifty/fifty chance it will stop with the LEDs lit. If they aren't, go ahead and press “Step Into” () until they are.
Step 4: Adding Bugs!
#include "msp430g2211.h" #define LED4 BIT1 #define LED3 BIT2 #define LED2 BIT3 #define LED1 BIT4 #define LED0 BIT5 #define BUZZER BIT6 unsigned int clicks = 0; //counter for each timer irq unsigned int seconds = 0; //counter for seconds unsigned int minutes = 0; //counter for minutes unsigned int new_minute = 0; //marker for reaching the next minute //configurable options! const unsigned int pomodoro_length = 25; //how long should the pomodoro be? [default: 25] const unsigned int pomodoro_break = 5; //and the break? [default: 5] //sets the LEDs to reflect the low 5 bits of 'value' void setLEDs (char value) { if(value & 0x10) P1OUT |= (LED4); // set LED4 on else P1OUT &= ~(LED4); //set LED4 off if(value & 0x08) P1OUT |= (LED3); // set LED3 on else P1OUT &= ~(LED3); //set LED3 off if(value & 0x04) P1OUT |= (LED2); // set LED2 on else P1OUT &= ~(LED2); //set LED2 off if(value & 0x02) P1OUT |= (LED1); // set LED1 on else P1OUT &= ~(LED1); //set LED1 off if(value & 0x01) P1OUT &= ~(LED0); // set LED0 on else P1OUT |= (LED0); //set LED0 off } void main( void ) { unsigned int pomodoro_remain = pomodoro_length + pomodoro_break; //counter to keep track of time passed WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer to prevent time out reset BCSCTL1 = CALBC1_1MHZ; // Set DCO to 1MHz factory calibration value DCOCTL = CALDCO_1MHZ; CCTL0 = CCIE; // CCR0 interrupt enabled CCR0 = 50000; // timer irq rate TACTL = TASSEL_2 + MC_1; // SMCLK, upmode _BIS_SR(GIE); // enable interrupt P1DIR |= (LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER); //all LED pins and buzzer as output P1OUT &= ~(LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER); //all LEDs and buzzer off while(pomodoro_remain) { //let's run this loop until the pomodoro is done if(minutes > new_minute) { //see if we have reached the next minute setLEDs(pomodoro_remain); //set the LEDs to reflect the current minute new_minute = minutes; //change our counter pomodoro_remain--; //less time remains in our pomodoro } } while(1){} //this is the song that never ends, some people started singing it... } // Timer A0 interrupt service routine // This runs when a timer interrupt is triggered #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { clicks++; // new click every 1Mhz/50k = 20 times a seconds if (clicks >= 2) { //we're using the number 2 here instead of 20 to //speed up debugging // if (clicks >= 20) { //waited a full second yet? clicks = 0; //then reset the counter.. seconds++; //advance second counter if(seconds >= 60) { //see if we have waited a minute? seconds = 0; //then reset second counter minutes++; //advance minute counter } } } |
Step 5: Breakpoints, Locals, and Registers
So, let's take a look at the 'setLEDs' function. Either it is receiving the wrong value or receiving the right value but displaying the wrong value (or some strange combination of both).
Scroll down to the first line of the setLEDs function (line 22, see image 1). Right-click any part of that line and select "Toggle Breakpoint (Code)". Notice the red dot to the left of that line of code? You can double click in that margin to set/unset breakpoints as well. (image 2) A breakpoint is a point the in the code where program execution will break allowing you to debug.
With the breakpoint set, press the "Reset" () button. After pressing "Reset" you'll be prompted with a long winded message that says it may take a long time to 'Run to main'. What this means is that your debugger is set to stop at the first line in the main function but your only hardware breakpoint is set at line 22. In order to stop at main, the program will execute one line at a time until it gets to main. Right now, that isn't a big problem, but this limitation can be troublesome with more complicated code. Note that more capable microcontrollers can allow many more hardware breakpoints, but one is better than nothing (yes, I'm look at you Arduino). :)
For now, just understand, that if a breakpoint is set when you start your code, it'll take an extra second or two to reach main.
Whew.. okay. The green arrow is at the first line in main now (line 50, see image 3)? Great, now we could single step (i.e. "Step Into" ()) all the way to the setLEDs function, but this could take a very long time. Instead, because we have a breakpoint, we just hit "Go" ().
Almost immediately, the green arrow scrolls to line 22. To get a better idea about what this function is doing, select “View” → “Locals” from the menu. (image 4) In the window that comes up, the local variable “value” is displayed with... err.. it's value, “0x1e”. Right-clicking on the variable in the locals window lets you change the base notation of the value. Change it to decimal and notice now that the function did indeed get passed the correct number (30).
Since this function only deals with a single variable and we know it is correct, let's close the “Locals” window. Now select “View” → “Register” from the menu. In the “Register” window, change the dropdown box to show “Port 1/2”. Expand the “P1OUT” tree. (image 5)
P.0 and P.7 may have random values but P.1 – P.6 should all be zero. Press “Step Into” () a few times. As we single step through the code, we can watch the registers change. You don't even need to look at the datasheet to know that the P1OUT register is the state of the digital pins on the microcontroller since the LEDs should be reflecting what you see in the “Register” window. Red values in the “Register” window mean that the last instruction executed changed that particular value. This applies to variables in the “Locals” window as well.
Note that the value only turns red if the register changes, so after executing line 45, the register is red and is showing a 1 instead of the expected 0. We found our bug! Looks like I reversed the binary operators. (image 6)
Step 6: On-the-fly Memory Changes!
The absolute best part of this? You can change memory on the fly simply by typing in the memory window! This is a fantastic feature, but this can really mess things up as well, so be careful (or at least ready to reboot the microcontroller). On-the-fly memory changes also apply to registers and variables in the locals window. Simply double-click the current values to change them to what you want to try!
Now that is cool, huh? Can your Arduino... nevermind.
I'm sorry Arduino, I really do think you're great... but In-Circuit Debugging is awesome!
Step 7: IAR Debug Menu Options
Command | Icon | Description |
---|---|---|
Go | Executes from the current statement or instruction until a breakpoint or program exit is reached. | |
Break | Stops the application execution. | |
Reset | Resets the target processor. | |
Stop Debugging | Stops the debugging session and returns you to the project manager. | |
Step Over | Executes the next statement, function call, or instruction, without entering C or C++ functions or assembler subroutines. | |
Step Into | Executes the next statement or instruction, entering C or C++ functions or assembler subroutines. | |
Step Out | Executes from the current statement up to the statement after the call to the current function. | |
Next Statement | Executes directly to the next statement without stopping at individual function calls. | |
Run to Cursor | Executes from the current statement or instruction up to a selected statement or instruction. |
Step 8: Super Plumber Pomodoro Timer
To make the finished product, in addition to the MSP430 and LEDs, you'll also need a piezo speaker/buzzer, a 3v battery, a 10k resistor, three 100 ohm resisters, a NPN & PNP transistor, and a switch. You can probably scavenge most of the parts from old/broken electronics or for less than a six-pack of good beer you can order everything, including the Launchpad from Digikey or Mouser.
Piezo speaker/buzzers can be found in most anything that makes sound. The one I'm using is from an old fax machine. The 3 volt battery can be a coin cell battery or two AA or AAA batteries in series. I used a 20mm coin battery holder only to realize later that I was out of 20mm coin batteries. The LEDs are scavenged right angle bulbs that have sat in my junk box for years.
The final schematic is below and should be fairly straight forward so I won't go into exact point A to point B connections.
Note the polarity on your batteries and LEDs. Doesn't matter for the speaker and resistors.
I didn't use a bypass capacitor or any of the resistors except R1 on my final version but included them in the schematic. If you're build isn't working correctly, adding C1 first and then the resistors may help.
Note that timing 30 minutes with an internally clocked uC is not the best practice. My final version actually takes 30:30 to run the entire pomodoro and would of course be temperature dependent.
As for the name "Super Plumber Pomodoro Timer", just watch the video :) This isn't recorded audio (as there would be no room for it) but generated by a piano note -> hertz lookup table. Thanks to youtube user ech0sdev for his MSP430 implementation that I stole and expanded ( http://www.youtube.com/watch?v=oEx_EgIlvtc ). Note that I believe his timing values are possibly for 8mhz (he said he wasn't clear about the timing) while mine are for 1mhz.
The madness with the transistors may not be clear, but it is a fun little mini circuit to breadboard if you want to play with it. It allows the MSP430 to kill it's own power.
Thank you all for reading this! The pomodoro timer is a little quick and dirty (power consumption could be MUCH improved) but hopefully you may have learned something about the power of In-Circuit Debugging.
Step 9: Super Plumber Pomodoro Timer Code!
#include "msp430g2211.h" #include "notes.h" unsigned int clicks = 0; unsigned int seconds = 0; unsigned int minutes = 0; unsigned int new_minute = 0; const unsigned int pomodoro_length = 25; const unsigned int pomodoro_break = 5; #define LED4 BIT1 #define LED3 BIT2 #define LED2 BIT3 #define LED1 BIT4 #define LED0 BIT5 #define BUZZER BIT6 #define POWER BIT7 void setLEDs (char value) { if(value & 0x10) P1OUT |= (LED4); // set LED4 on else P1OUT &= ~(LED4); //set LED4 off if(value & 0x08) P1OUT |= (LED3); // set LED3 on else P1OUT &= ~(LED3); //set LED3 off if(value & 0x04) P1OUT |= (LED2); // set LED2 on else P1OUT &= ~(LED2); //set LED2 off if(value & 0x02) P1OUT |= (LED1); // set LED1 on else P1OUT &= ~(LED1); //set LED1 off if(value & 0x01) P1OUT |= (LED0); // set LED0 on else P1OUT &= ~(LED0); //set LED0 off } void main( void ) { unsigned int pomodoro_remain = pomodoro_length + pomodoro_break; WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer to prevent time out reset BCSCTL1 = CALBC1_1MHZ; // Set DCO to 1MHz factory calibration value DCOCTL = CALDCO_1MHZ; P1DIR |= (LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER + POWER); //all LED pins and buzzer as output P1DIR |= POWER; //POWER remains on until we want to kill our own power P1OUT &= ~(LED0 + LED1 + LED2 + LED3 + LED4 + BUZZER); //all LEDs and buzzer off CCTL0 = CCIE; // CCR0 interrupt enabled CCR0 = 50000; // clock divisor TACTL = TASSEL_2 + MC_1; // SMCLK, upmode _BIS_SR(GIE); // enable interrupt setLEDs(pomodoro_remain); playTheme(); while(pomodoro_remain) { if(minutes > new_minute) { new_minute = minutes; pomodoro_remain--; setLEDs(pomodoro_remain); if(pomodoro_remain == pomodoro_break) { playFlagTune(); } else { beep(G2, 124); // .25 beep, except when we reach the break } } } play1up(); //final chime P1OUT &= ~(POWER); //set POWER off // Sleep forever... shouldn't reach this, but who knows? :) _BIS_SR(LPM1_bits + GIE); } // Timer A0 interrupt service routine #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { // we will get called at 1Mhz/50k = 20 times a seconds clicks++; if (clicks >= 20) { clicks = 0; seconds++; // advance clock for each second if(seconds >= 60) { seconds = 0; minutes++; } } } |
notes.h
//this shouldn't be here, but I was in a hurry to submit //before the contest deadline ran out :) #define BUZZER BIT6 //Definition of the notes' frequecies in Hertz. #define C8 4186 #define B7 3951 #define As7 3729 #define A7 3520 #define Gs7 3322 #define G7 3136 #define Fs7 2960 #define F7 2794 #define E7 2637 #define Ds7 2489 #define D7 2349 #define Cs7 2217 #define C7 2093 #define B6 1976 #define As6 1865 #define A6 1760 #define Gs6 1661 #define G6 1568 #define Fs6 1480 #define F6 1397 #define E6 1319 #define Ds6 1245 #define D6 1175 #define Cs6 1109 #define C6 1047 #define B5 988 #define As5 932 #define A5 880 #define Gs5 831 #define G5 784 #define Fs5 740 #define F5 698 #define E5 659 #define Ds5 622 #define D5 587 #define Cs5 554 #define C5 523 #define B4 494 #define As4 466 #define A4 440 #define Gs4 415 #define G4 392 #define Fs4 370 #define F4 349 #define E4 330 #define Ds4 311 #define D4 294 #define Cs4 277 #define C4 262 #define B3 247 #define As3 233 #define A3 220 #define Gs3 208 #define G3 196 #define Fs3 185 #define F3 175 #define E3 165 #define Ds3 156 #define D3 147 #define Cs3 139 #define C3 131 #define B2 123 #define As2 117 #define A2 110 #define Gs2 104 #define G2 98 #define Fs2 92 #define F2 87 #define E2 82 #define Ds2 78 #define D2 73 #define Cs2 69 #define C2 65 #define B1 62 #define As1 58 #define A1 55 #define Gs1 52 #define G1 49 #define Fs1 46 #define F1 44 #define E1 41 #define Ds1 39 #define D1 37 #define Cs1 35 #define C1 33 #define B0 31 #define As0 29 #define A0 28 void delay_ms(unsigned int ms ) { unsigned int i; for (i = 0; i<= ms; i++) __delay_cycles(1000); } void delay_us(unsigned int us ) { unsigned int i; for (i = 0; i<= us; i++) __delay_cycles(1); } //This function generates the square wave that makes the piezo speaker sound at a determinated frequency. void beep(unsigned int note, long duration) { long delay = (long)(62500/note); //This is the semiperiod of each note. long time = (long)((duration*100)/delay); //This is how much time we need to spend on the note. for (long i = 0; i < time; i++) { P1OUT |= BUZZER; //Set buzzer on... delay_us(delay); //...for a semiperiod... P1OUT &= ~BUZZER; //...then reset it... delay_us(delay); //...for the other semiperiod. } } void playTheme(void) { //theme //4|ee-e-ce-g---------||c----------------e-ga-fg-e-cd----|| //3|-------------g----||---g--e--a-b-Aa-g------------b---|| //2|-------------g----||---------------------------------|| beep(E4, 124); beep(E4, 124); delay_ms(124); beep(E4, 124); delay_ms(124); beep(C4, 124); beep(E4, 124); delay_ms(124); beep(G4, 124); delay_ms(496); beep(G3, 124); delay_ms(496); beep(C4, 124); delay_ms(248); beep(G3, 124); delay_ms(248); beep(E3, 124); delay_ms(248); beep(A3, 124); delay_ms(124); beep(B3, 124); delay_ms(124); beep(As3, 124); beep(A3, 124); delay_ms(124); beep(G3, 124); beep(E4, 124); delay_ms(124); beep(G4, 124); beep(A4, 124); delay_ms(124); beep(F4, 124); beep(G4, 124); delay_ms(124); beep(E4, 124); delay_ms(124); // beep(C4, 124); beep(D4, 124); beep(B4, 124); // sounds better without the last line unless looped } void play1up(void) { //1up sound beep(E5, 62); delay_ms(62); beep(G5, 62); delay_ms(62); beep(E6, 62); delay_ms(62); beep(C6, 62); delay_ms(62); beep(D6, 62); delay_ms(62); beep(G6, 312); } void playFlagTune (void) { //flag* beep(G2, 124); beep(C3, 124); beep(E3, 124); beep(G3, 124); beep(C4, 124); beep(E4, 124); beep(G4, 372); beep(E4, 372); beep(Gs2, 124); beep(C3, 124); beep(Ds3, 124); beep(Gs3, 124); beep(C4, 124); beep(Ds4, 124); beep(Gs4, 372); beep(Ds4, 372); beep(As2, 124); beep(D3, 124); beep(F3, 124); beep(As3, 124); beep(D4, 124); beep(F4, 124); beep(As4, 372); beep(As4, 124); beep(As4, 124); beep(As4, 124); beep(E4, 372); } |