13 September 2011

Tutorial 14b: Adding a New Library

Before I start this tutorial, let me add a caveat: I have a feeling this is not the best way to build a library in CCS. It is, however, the only way I could get it to work reliably short of copying the code into every project I use it in. If anyone has some experience with this in CCS, please send a comment and let me know!

We have code that will let us easily send text to the LCM, which would be very useful to have in a library that can be called up as needed, without having to rewrite (or copy-paste) the code every time. The C language makes doing this fairly easy, and so we'll look now at moving the LCM code into a library and go through how to configure a project in CCS to use the library. You should be able to add any code you'd like to reuse to this library and be able to call it up whenever needed.

First: choose a location to keep your library. It's not important where this library resides (from the compiler's point of view), but it's best to have it somewhere easy to get to when you add/change code in your library. At the same time, it should be somewhere safe, where it won't be accidentally deleted, moved, or changed in any unintentional way. I chose to create a folder in my workspace directory called 'library'.

Second: copy any #include, #define, function prototypes and global variables into a new header file. For this library, I've called it simple_LCM.h. If you're going to use definitions specific to the MSP430, you will need to include the MSP430 header as well. To keep your library general, rather than including the header file for a specific device, just #include <msp430.h>.

Third: copy the remaining code (the encapsulated functions) into a new .c file with the same name (ie. simple_LCM.c in this case). At the top of the file, you should add #include <filename.h> (replacing filename with the name of your library file). Note that this file should not have a main function in it.

Fourth: in your new project, right click the project folder and select new → folder. Click the [Advanced >>] button, and select "Link to folder in the filesystem". You can then browse to your library folder and finish adding the folder.

Any files in your library directory are now available for use in your code; the compiler, however, needs to be aware of the path to this folder to find it. (This is the part I don't like; this has to be done for every project, and I'm unable to find a way to make this path be a default in CCS for every new project.)

Fifth: right click your project folder and select properties. Open the C/C++ Build window, and in the Tool Settings, look for MSP430 Compiler → Include Options as well as MSP430 Linker → File Search Path. Both of these need to have your library folder added to the list in order to compile your code.

One shortcut I've found: In CCS, go to the menu Window → Preferences, then navigate to General → Workspace → Linked Resources. Here you can define a path variable (eg. My_Library) that links to your library directory. When you add a new folder to a project, instead of browsing to the folder location, you can click [Variables...] and select it from the list; it's much quicker that way. Unfortunately, I can't seem to get the project properties changes to recognize the path variable, though it seems it's supposed to.

Now we should be ready to build our capacitance meter using the LCM. The code I've written in CMeterLCMG2211.c demonstrates a number of new ideas using the LCM. Browse the code and examine the comments to see how it works. Note the use of MoveCursor(row,col); and the particular commands sent to configure the LCM.

While the simple_LCM library has a routine for printing strings, what happens when we want to print an integer value like the recorded value in the time variable? One intuitive option (at least if you're accustomed to programming in C) would be to use the stdio library and the function sprintf();. All we would need to do is set up a character array such as print_time[10], and use sprintf(print_time, "%d", time); to put the integer into the print_time string and pass it to PrintStr(). Unfortunately, this method has some serious problems for microcontroller use. First of all, even with the heavy streamlining done in CCS to reduce its size, any code using a printf function will be large. In this program, it would exceed the 2 kB of size available in this device. Second, the streamlining makes it difficult to format correctly; ideally, we'd use a %10d format specifier to put time into exactly 10 places to fit the print_time size. We can't do this with the streamlining implemented. We can change the printf assumptions in the project properties, but that makes the function use even more of our severely limited code space.

Fortunately, there are some ways around this problem. For an integer, we can pick off the individual digits by using the mod operator and integer division. x%10; will return the last digit of the number stored in x. x/=10; will remove the last digit and leave up to the second to last. By running a loop over the number until we reach a condition of x == 0 (no more digits), we can pick off each digit to print one by one. The ASCII codes (and the codes for the LCM) are arranged in a way such that the lower nibble corresponds exactly to the digit's value, so 0x30 + 0 is "0", 0x30 + 7 is "7", and so on.

The disadvantage to this loop technique is that the digits are picked off in reverse order--from right to left. The LCM has a mode that allows you to decrement the cursor position when you send characters, however, so it's possible to print from right to left in this way. (In fact, this ability is used in many hand-held calculators.) See the code for the exact code needed to configure the LCM for this mode.

And there's our first complete scientific instrument using the MSP430. We use a combination of the timer and comparator with a calibrated clock to measure the decay time in an RC circuit. The LCM displays the measured time in microseconds. Knowing the value of R and the reported time, we can calculate the actual value of C measured by the meter.

Reader Exercise: This works fine, but wouldn't it be nice to have the LCM display the capacitance rather than the time? You can do floating point operations in the MSP430 (albeit inefficiently), but how would you display a floating point number on the LCM? If sprintf was to big for the program above, it will definitely be too large in this case. Can you come up with a way to display the capacitance without exceeding the 2 kB limit for the G2211 device? If you get stuck, one way is demonstrated in CMeterLCMFull.c. It also has the benefit of being auto-ranging. This code takes up 1934 bytes of space-- just barely enough to squeeze into the G2211!

3 comments:

Jon said...

David,

I am using the reverse printing code you've used here in a project I am working on. I have a potentiometer that has a range of 220 degrees so I am adding 70 degrees to the adjust for the offset. The value displays to the LCD and updates correctly with the exception of the following. Every time I drop below 100 degrees the display keeps a 1 at the front of the readout. Instead of 99.99 I get 199.99. I've combed over my code and can't figure it out. I was wondering if you could take a quick look.

#pragma vector = ADC10_VECTOR
__interrupt void YawAngleISR(void)
{
unsigned int wholeNumber, decimalNumber;

yawAngle = ((ADC10MEM * YAW_MULTIPLIER) + 70.0);

wholeNumber = (int)yawAngle;
decimalNumber = (int)((yawAngle - wholeNumber) * 100);

// Set up the LCD to print in decrementing mode
moveCursor(1,15);
__delay_cycles(10000);
sendByte(0x04, COMMAND);

sendByte(0xDF, CHARACTER); // degree symbol
__delay_cycles(10000);

// Print the decimal portion of the yaw angle
while (decimalNumber > 0)
{
sendByte(0x30 + (decimalNumber%10), CHARACTER);
decimalNumber /= 10;
}

//Print the "."
sendByte(0x2E, CHARACTER);
__delay_cycles(10000);

// Print the whole number of the yaw angle
while (wholeNumber > 0)
{
sendByte(0x30 + (wholeNumber%10), CHARACTER);
wholeNumber /= 10;
}

__delay_cycles(10000);
sendByte(0x06, COMMAND); // increment mode
}

Jon said...

Haha. I literally figured it out 10 seconds after I sent it. I am getting the 1 because nothing has told the LCD to erase the 1! Sorry to bother you.

Take care,

Jon

Jon said...

For anyone interested here is the fix.

#pragma vector = ADC10_VECTOR
__interrupt void YawAngleISR(void)
{
unsigned int wholeNumber, decimalNumber; // These will be used to print the number to the LCD

char lessThen100 = 0; // Use this to check for values less then 100

yawAngle = ((ADC10MEM * YAW_MULTIPLIER) + 70.0);

wholeNumber = (int)yawAngle;
decimalNumber = (int)((yawAngle - wholeNumber) * 100);

// Set up the LCD to print in decrementing mode
moveCursor(1,15);
__delay_cycles(10000);
sendByte(0x04, COMMAND);

sendByte(0xDF, CHARACTER); // degree symbol
__delay_cycles(10000);

// Print the decimal portion of the yaw angle
while (decimalNumber > 0)
{
sendByte(0x30 + (decimalNumber%10), CHARACTER);
decimalNumber /= 10;
}

//Print the "."
sendByte(0x2E, CHARACTER);
__delay_cycles(10000);

// Print the whole number portion of the yaw angle
while (wholeNumber > 0)
{
lessThen100++; //increment char

sendByte(0x30 + (wholeNumber%10), CHARACTER);
wholeNumber /= 10;
}

__delay_cycles(10000);

// Check to see if the char = 2. If it does the value is less then 100. Move the cursor to the position and print a " ".
if (lessThen100 == 2)
{
moveCursor(1,9);
__delay_cycles(10000);
sendByte(0x20, CHARACTER);
__delay_cycles(10000);
}

__delay_cycles(10000);
sendByte(0x06, COMMAND); // Return to increment mode

}