Tuesday, July 1, 2014

MSP430F5529 LaunchPad 'Project0.1'

Quite awhile ago I wrote a little tutorial on how to do the MSP430F5529 'Project0' on Fedora 20. At that time, I promised a follow-up tutorial on using mspdebug. This isn't that tutorial. This is 'Project0' again, but showing using multiple source files, a header file, and cleaning up all but one compiler warning. The mspdebug tutorial is coming 'soon'.




I hate tutorials that start off with bad practices. In C, we use header files. C code (really, all code IMHO) should be explicit in terms of variable definition. Nobody should be left guessing if an int is 8 bits, or 16 bits. At the same time, it's my preference that the 'main' method be in it's own file, and be fairly clutter free.

So, taking the above basic guidelines into consideration, let's refactor 'Project0'.

First, let's split our delay function out of the main method. Make a 'delay.c' file in your project folder. The file should look like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "delay.h"

//Tell GCC to not optimize out the delay loop
#pragma GCC push_options
#pragma GCC optimize ("0")
void delay(uint16_t delayCounts) {
 uint16_t i = 0;

 //Delay between LED toggles. This for-loop will run until the condition is met.
 //In this case, it will loop delayCounts number of times
 for(i=0; i < delayCounts; i++);
}
#pragma GCC pop_options

A couple of things to note. First, we are including a 'delay.h' header file, we'll go over that shortly. Also notice our delay function now takes an argument, 'delayCounts', that allows us to specify how long to delay for. The type of delayCounts is 'uint16_t'. This is a C99 integer type, the 'u' means it is unsigned, 'int' means integer, '16' means it is 16 bits wide (the full set of specifications is available here). The for loop has been modified to iterate delayCounts times.

Now let's make the 'delay.h' header file. It should contain the following:

1
2
3
4
5
6
7
8
#ifndef DELAY_H
#define DELAY_H

#include <stdint.h>

void delay(uint16_t);

#endif

Notice the '#ifndef', '#endif' block. We use this to prevent any of our header file from being included in the project multiple times. This allows us to include delay.h in as many source files as we want and not worry about multiple definition errors.

In the actual meat of the header file, we include '<stdint.h>' which gets us the C99 integer types we discussed previously. Finally, we give a prototype of our delay function.

Next, let's modify our 'main.c' file as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <msp430.h>
#include <stdbool.h>
#include "delay.h"

int main(void) {
 WDTCTL = WDTPW + WDTHOLD;
 // Stop watchdog timer. This line of code is needed at the beginning of most MSP430 projects.
 // This line of code turns off the watchdog timer, which can reset the device after a certain period of time.
 P1DIR |= 0x01;
 // P1DIR is a register that configures the direction (DIR) of a port pin as an output or an input.
 // To set a specific pin as output or input, we write a '1' or '0' on the appropriate bit of the register.
 // P1DIR = <pin7><pin6><pin5><pin4><pin3><pin2><pin1><pin0>
 // Since we want to blink the on-board red LED, we want to set the direction of Port 1, Pin 0 (P1.0) as an output
 // We do that by writing a 1 on the PIN0 bit of the P1DIR register
 // P1DIR = <pin7><pin6><pin5><pin4><pin3><pin2><pin1><pin0>
 // P1DIR = 0000 0001
 // P1DIR = 0x01 <--this is the hexadecimal conversion of 0000 0001

 while(true)
 // Conditional is always true, so we loop forever
 {
  P1OUT ^= 0x01;
  // Toggle P1.0 using exclusive-OR operation (^=)
  // P1OUT is another register which holds the status of the LED.
  // '1' specifies that it's ON or HIGH, while '0' specifies that it's OFF or LOW
  // Since our LED is tied to P1.0, we will toggle the 0 bit of the P1OUT register
  delay(20000);
 }

 return 0;
}

We added a couple of includes, the first ('<stdbool.h>') gives us the C99 'true' and 'false' macros (specifics here). We use this to modify our main loop. We also include our delay header file.

Now, notice we have given our main method a return type of 'int'. This is simply to prevent compiler warnings. We don't use a C99 int type here because they would trigger the same "warning: return type of 'main' is not 'int'" we are trying to prevent.

We also changed the for loop in main to a while loop. This is purely a personal preference thing, but I like my infinite loops to be of the while variety. A while loop continues until the conditional evaluates to 0. Since the 'true' macro defined in stdbool.h converts to 1, the loop continues indefinitely.

Finally, we specify a return value of 0. This isn't strictly required, but since we changed main to have a return value, it's nice to provide an explicit one.

To make all of this play nicely together, we change the "SOURCES = main.c" line of 'Makefile' to be "SOURCES= main.c delay.c". The compiler is smart enough to handle the header for us.

Since having to use the mspdebug CLI every time we want to write to the device is a bit of a drag, we all add a 'make install' option to the Makefile. First, define the mspdebug and driver types in the Makefile as follows:

1
2
3
# List mspdebug options
MSPDEBUG   = mspdebug
DRIVER     = tilib

Add the install section to the Makefile, this simply compiles our program to an elf file as we normally would, then writes it using the mspdebug command and driver we defined earlier.

1
2
install: $(TARGET).elf
 $(MSPDEBUG) $(DRIVER) "prog $(TARGET).elf"

For the sake of being thorough, the whole Makefile is presented below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#
# Makefile for msp430
#
# 'make' builds everything
# 'make clean' deletes everything except source files and Makefile
# 'make install' builds everything, and programs the MSP430 using MSPDEBUG and DRIVER
# You need to set TARGET, MCU and SOURCES for your project.
# TARGET is the name of the executable file to be produced
# $(TARGET).elf $(TARGET).hex and $(TARGET).txt nad $(TARGET).map are all generated.
# The TXT file is used for BSL loading, the ELF can be used for JTAG use
#
TARGET     = project0
MCU        = msp430f5529
# List mspdebug options
MSPDEBUG   = mspdebug
DRIVER     = tilib
# List all the source files here
# eg if you have a source file foo.c then list it here
SOURCES = main.c delay.c
# Include are located in the Include directory
INCLUDES = -IInclude
# Add or subtract whatever MSPGCC flags you want. There are plenty more
#######################################################################################
CFLAGS   = -mmcu=$(MCU) -g -Os -Wall -Wunused $(INCLUDES)
ASFLAGS  = -mmcu=$(MCU) -x assembler-with-cpp -Wa,-gstabs
LDFLAGS  = -mmcu=$(MCU) -Wl,-Map=$(TARGET).map
########################################################################################
CC       = msp430-gcc
LD       = msp430-ld
AR       = msp430-ar
AS       = msp430-gcc
NM       = msp430-nm
OBJCOPY  = msp430-objcopy
RANLIB   = msp430-ranlib
STRIP    = msp430-strip
SIZE     = msp430-size
READELF  = msp430-readelf
MAKETXT  = srec_cat
CP       = cp -p
RM       = rm -f
MV       = mv
########################################################################################
# the file which will include dependencies
DEPEND = $(SOURCES:.c=.d)
# all the object files
OBJECTS = $(SOURCES:.c=.o)
all: $(TARGET).elf $(TARGET).hex $(TARGET).txt
$(TARGET).elf: $(OBJECTS)
 echo "Linking $@"
 $(CC) $(OBJECTS) $(LDFLAGS) $(LIBS) -o $@
 echo
 echo ">>>> Size of Firmware <<<<"
 $(SIZE) $(TARGET).elf
 echo
%.hex: %.elf
 $(OBJCOPY) -O ihex $< $@
%.txt: %.hex
 $(MAKETXT) -O $@ -TITXT $< -I
 unix2dos $(TARGET).txt
#  The above line is required for the DOS based TI BSL tool to be able to read the txt file generated from linux/unix systems.
%.o: %.c
 echo "Compiling $<"
 $(CC) -c $(CFLAGS) -o $@ $<
# rule for making assembler source listing, to see the code
%.lst: %.c
 $(CC) -c $(ASFLAGS) -Wa,-anlhd $< > $@
# include the dependencies unless we're going to clean, then forget about them.
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPEND)
endif
# dependencies file
# includes also considered, since some of these are our own
# (otherwise use -MM instead of -M)
%.d: %.c
 echo "Generating dependencies $@ from $<"
 $(CC) -M ${CFLAGS} $< >$@
.SILENT:
.PHONY: clean
clean:
 -$(RM) $(OBJECTS)
 -$(RM) $(TARGET).*
 -$(RM) $(SOURCES:.c=.lst)
 -$(RM) $(DEPEND)
install: $(TARGET).elf
 $(MSPDEBUG) $(DRIVER) "prog $(TARGET).elf"

Once you've made these changes, you can compile the project and write it to the LaunchPad by simply typing 'make install', or using the method described in the first tutorial. If everything has gone well, the project should compile with only a single warning, "warning: #pragma GCC target is not supported for this machine [-Wpragmas]" which is due to us 'popping' compiler options which technically aren't supported. This is fixable, but significantly more work that it's worth.

Update 2015-10-13:
I have uploaded the above files (updated to work with the newer versions of mspgcc I package) to a BitBucket snippet.

No comments:

Post a Comment