Good links, Click_here, and thanks for the kind words. Although some like my detailed answers, I do know they annoy others immensely; they do tend to be *walls of text*. Sorry about that.

There are also specific tutorials and information on SPI with MSP430G2 microcontrollers here, here, here and here, and lots more.

Those should not only show *totallyclueless* and other MSP430G2 microcontroller beginners how others have done similar stuff successfully, but also help understand how stuff works in the microcontroller world in general, and how you can get them to do stuff you yourself want. Ideas maturing and bubbling in your subconscious is always a good thing.

You'll find information like how to select the proper SPI transfer mode (the polarity of the clock the LCD unit prefers, signal timing), how to set up the SPI transfers, and so on. I'll assume you'll visit those, and won't cover any of it.

Also, the MSP430G2 USI SPI can transfer "words" of any bit length (up to 16 bits) at a time. For this use case, I do believe 8 bit units work best.

It also has two separate SPI busses, so using a 16-bit SPI ADC would be even easier than I initially thought with this specific microcontroller; most have only one SPI bus. With MSP430G2, you can put the LCD module and the ADC on separate SPI busses, making configuration and use much simpler. No chip enable I/O lines needed, for example.

*Totallyclueless*, if you look at the bit map, you'll notice that each LCD digit, including the decimal point to the left of the digit, is a separate 8-bit byte. Therefore, the main task is to construct the ten digits (0, 1, .., 9) in one byte.

(In other words, the SPI transfers to the DDM4 LCD module are not just/really 32 bits, it is four groups of eight bits each, first group defining the leftmost digit/element, and the last/fourth group defining the rightmost digit/element. Because of this, it is easier to treat it as four separate elements, rather than one large 32-bit element.)

Using byte values you can sum together, the parts of each LCD digit are

Code:

| 32 20
| 64 16 40 10
| 128 or, in hex: 80
| 2 8 02 08
| 1 4 01 04

Therefore, the byte value describing each decimal digit are

Code:

Blank: = 0
For zero "0": 2 + 4 + 8 + 16 + 32 + 64 = 126
For one "1": 8 + 16 = 24
For two "2": 2 + 4 + 16 + 32 + 128 = 182

and so on. Note that it's much easier to calculate these in hexadecimal, since for example three, "3", is 0x04 + 0x08 + 0x10 + 0x20 + 0x80 = 0xBC = 188.

In your C code, the array for a single digit is simple:

Code:

/* Decimal point is additive to the digit */
#define DIGIT_DOT 1
static const unsigned char digit[14] = {
126, /* "0" */
24, /* "1" */
182, /* "2" */
188, /* "3" */
/* digits 4 thorough 9 omitted for brevity */
128, /* digit[10] = "-", minus sign */
0, /* digit[11] = " ", space (empty) */
230, /* digit[12] = "E" */
130, /* digit[13] = "r" */
};

Let's assume you have a very nice software calibration setup:

Code:

/* Minimum ADC sample, and corresponding displayed value.
*/
static uint16_t minimum_adc = 0;
static int16_t minimum_lcd = 0; /* -999 to 9999 */
/* Maximum ADC sample, and corresponding displayed value.
*/
static uint16_t maximum_adc = 255; /* Assuming 8-bit ADC */
static int16_t maximum_lcd = 100; /* -999 to 9999 */
/* Note: minimum_adc < maximum_adc, or you'll get garbage out.
* If you want inverted output, swap the _lcd values instead!
*/

Let's write the outlines of the function that computes the four bytes sent to the LCD module -- let's save them in lcd_byte[] array for now --, based on the ADC value.

Code:

/** adc_to_lcd() - Scale ADC sample to displayed value range.
*/
static int16_t adc_to_lcd(uint16_t adc)
{
const int16_t range_lcd = maximum_lcd - minimum_lcd;
/* Clamp adc to minimum..maximum range, inclusive. */
if (adc < minimum_adc)
adc = minimum_adc;
else
if (adc > maximum_adc)
adc = maximum_adc;
/* Due to the multiplication, we need 32-bit math.
* The C compiler can handle this even on 8-bit MCUs,
* although the computation is relatively slow there
* (hundreds of cycles). */
return ( (int32_t)(adc - minimum_adc) * (int32_t)range_lcd
+ (range_lcd / 2) /* for correct rounding */
) / (maximum_adc - minimum_adc)
+ minimum_lcd;
}
/** lcd_encode_number() - Encode a number -999 to 9999 into LCD bytes.
* @value: Between -999 and 9999
* @decimals: Number of decimals, 0 to 4 (3 if negative).
* If zero, no decimal point is shown.
*/
static unsigned char lcd_byte[4] = { 0U, 0U, 0U, 0U };
static void lcd_encode_number(const int16_t value, const char decimals)
{
/* The LCD display module is limited to -999 to 9999
* (for integersAlthough the above should always produce values only within
* the minimum_lcd .. maximum_lcd (inclusive) range,
* the display is limited to -999 to 9999.
* If we exceed the range, we display "-Err" or " Err".
*/
if (value_lcd < -999) {
/* Value is too small. Display "-Err". */
lcd_byte[0] = digit[10];
lcd_byte[1] = digit[12];
lcd_byte[2] = digit[13];
lcd_byte[3] = digit[13];
} else
if (value_lcd < -99) {
/* We have "-XXX". */
lcd_byte[0] = digit[10]; /* digit[10] is '-' */
lcd_byte[1] = digit[value_lcd / -100]; /* Hundreds */
lcd_byte[2] = digit[(value_lcd / -10) % 10]; /* Tens */
lcd_byte[3] = digit[(-value_lcd) % 10]; /* Ones */
/* Decimal point? */
if (decimals >= 1 && decimals <= 3)
lcd_byte[4 - decimals] |= DIGIT_DOT;
} else
if (value_lcd < -9) {
/* We have " -XX". */
lcd_byte[0] = digit[11]; /* digit[11] is ' ' */
lcd_byte[1] = digit[10]; /* digit[10] is '-' */
lcd_byte[2] = digit[value_lcd / -10]; /* Tens */
lcd_byte[3] = digit[(-value_lcd) % 10]; /* Ones */
/* Decimal point? */
if (decimals >= 1 && decimals <= 2)
lcd_byte[4 - decimals] |= DIGIT_DOT;
} else
if (value_lcd < 0) {
/* We have " -X". */
lcd_byte[0] = digit[11]; /* digit[11] is ' ' */
lcd_byte[1] = digit[11]; /* digit[11] is ' ' */
lcd_byte[2] = digit[10]; /* digit[10] is '-' */
lcd_byte[3] = digit[-value_lcd]; /* Ones */
/* Decimal point? */
if (decimals == 1)
lcd_byte[3] |= DIGIT_DOT;
} else
if (value_lcd < 10) {
/* We have " X". */
lcd_byte[0] = digit[11]; /* digit[11] is ' ' */
lcd_byte[1] = digit[11]; /* digit[11] is ' ' */
lcd_byte[2] = digit[11]; /* digit[11] is '-' */
lcd_byte[3] = digit[value_lcd]; /* Ones */
/* Decimal point? */
if (decimals == 1)
lcd_byte[3] |= DIGIT_DOT;
} else
if (value_lcd < 100) {
/* We have " XX". */
lcd_byte[0] = digit[11]; /* digit[11] is ' ' */
lcd_byte[1] = digit[11]; /* digit[11] is ' ' */
lcd_byte[2] = digit[value_lcd / 10]; /* Tens */
lcd_byte[3] = digit[value_lcd]; /* Ones */
/* Decimal point? */
if (decimals >= 1 && decimals <= 2)
lcd_byte[4 - decimals] |= DIGIT_DOT;
} else
if (value_lcd < 1000) {
/* We have " XXX". */
lcd_byte[0] = digit[11]; /* digit[11] is ' ' */
lcd_byte[1] = digit[value_lcd / 100]; /* Hundreds */
lcd_byte[2] = digit[(value_lcd / 10) % 10]; /* Tens */
lcd_byte[3] = digit[value_lcd]; /* Ones */
/* Decimal point? */
if (decimals >= 1 && decimals <= 3)
lcd_byte[4 - decimals] |= DIGIT_DOT;
} else
if (value_lcd < 10000) {
/* We have "XXXX". */
lcd_byte[0] = digit[value_lcd / 1000]; /* Thousands */
lcd_byte[1] = digit[value_lcd / 100]; /* Hundreds */
lcd_byte[2] = digit[(value_lcd / 10) % 10]; /* Tens */
lcd_byte[3] = digit[value_lcd]; /* Ones */
/* Decimal point? */
if (decimals >= 1 && decimals <= 4)
lcd_byte[4 - decimals] |= DIGIT_DOT;
} else {
/* The value is too large. Show " Err". */
lcd_byte[0] = digit[11];
lcd_byte[1] = digit[12];
lcd_byte[2] = digit[13];
lcd_byte[3] = digit[13];
}
}

You normally call the above using lcd_encode_number(adc_to_lcd( ADC ), 0) where ADC is the function or expression needed to obtain the next ADC sample.

It is a good idea to split it into two functions, because that way you now have another function you can reuse later on, one that can encode any number (-999 to 9999, -99.9 to 999.9, -9.99 to 99.99, -.999 to 9.999, and .0 to .9999, inclusive) to the four bytes you send to your LCD module.

Note that it is very simple to write a lcd_encode_float() function in almost exactly the same way, to encode a floating-point number to the same LCD module. However, users do not like if the decimal point jumps here and there; the decimals approach -- basically a fixed negative power of ten multiplier -- is much more user-friendly, visually. Play with your favourite multimeter, and you'll see why.

(For example, measure the resistance of some mixed bag of resistors. If the dot would jump, it'd be much easier to misread the display. On the other hand, automatic power-of-ten scale detection would be nice; on mine, I need to twist a knob to get to different scales.)

I'd also recommend you study the lcd_encode_number() function, until you understand exactly what it does. (The % operator is the modulus operator in C, and yields the remainder after division.)

There are two approaches to multi-byte (multi-word) SPI transfers:

- delay-driven, where you wait in a busy-wait loop for the SPI transfer to complete.

This is very easy to implement, but wastes both CPU power (you do nothing while the SPI transfer is being done), as well as current (you do not normally shift to a low-power state, and keep spinning in high-power state doing nothing). - interrupt-driven, where you load each byte to a buffer register, start the transfer, and have an interrupt handler function load the following bytes to the buffer register.

This is often a bit too steep to learn early in programming, as one must first understand the concepts and the patterns behind this approach. Hard at first, but rewarding.

The TI SPI examples, I believe, explicitly use the interrupt-driven approach for SPI.

I don't have an MSP420G2 at hand, so I cannot (do not want to, without hardware to play with) help you with the details on either approach. Even if I had, I'd use GCC and open-source tools in Linux, exclusively. With microcontrollers, it is best if you're tutored by someone with the same tools as you have -- do a web search on MSP420G2 and your development environment keywords to find info best suited for you --, as then you don't stumble on those small, but very annoying differences between the development environments.

Further questions?