This page is a companion page for my MSP430 Launchpad page.
I have a project I’m working on to build a bench power supply. I’ve been inspired by by the series of videos over at EEVBlog, starting with http://www.eevblog.com/2011/11/28/eevblog-221-lab-power-supply-design-part-1/.
For my power supply, I’ve decided not to go with microcontroller control (his ultimate goal), but I do intend to use an msp430g2553 to monitor voltages, drive an LCD display, and communicate with a PC via TTL level UART.
Most of my UART experience has been bit-banging with the less capable chips like the msp430g2211, so this is my chance to explore USCI. My first program here uses simple polling. Not power efficient, but straightforward.
1. USCI on the MSP430 with polling
The most complicated part of this program is initializing the UART parameters. Actually sending data is a simple matter of filling a register, but the clocks and settings all have to be ready to go first. The MSP430x2xx Family User’s Guide (Rev. I) contains appropriate values (in Table 15-4) for setting up USCI for different clock speeds and various baud rates. I chose 9600 baud, which will be plenty fast for my eventual application, clocked from a 1MHz clock.
A 1MHz clock doesn’t divide evenly to give timings for 9600 bits per second, since 1,000,000/9600 = 104.1666667. The table gives a setting of UCBRSx = 1 to adjust the timings that come from using a (slightly too fast) window size of 104 clock cycles. That helps keep things from getting out of sync over time.
One gotcha with the USCI support in the MSP430 series is that it reverses the notion of TXD and RXD from the pins they are traditionally assigned to with USI (and from the silk screens that you may see printed on your Launchpad card). For USCI, you need to connect TXD on P1.2 and RXD on P1.1. |
#include "msp430g2553.h" void uart_init( void ) { /** * 1. Set UCSWRST * 2. Initialize all USCI registers * 3. Configure ports * 4. Clear UCSWRST * 5. Enable interrupts via UCxRXIE and/or UCxTXIE (optional) */ // (1) Set state machine to the reset state UCA0CTL1 = UCSWRST; // (2) Initialize USCI registers UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK UCA0BR0 = 104; // 1MHz/9600 = 104 UCA0BR1 = 0x00; // UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 // (3) Configure ports P1SEL = BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD P1SEL2= BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD // (4) Clear UCSWRST flag UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine** // (5) Enable interrupts } void uart_send_byte( unsigned char data ) { // Wait until USCI_A0 TX buffer is ready while (!(IFG2&UCA0TXIFG)) ; // Send the byte UCA0TXBUF = data; } void main(void) { unsigned int i=0; WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog timer // The UART settings used depend on a good 1MHz clock BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1DIR &= ~BIT3; // P1.3 is an input pin P1IE |= BIT3; // Switch S2 triggers an interrupt P1IES |= BIT3; // on falling edge uart_init(); while( 1 ) { for (i=97; i<123; i++) { // send abcde...xyz uart_send_byte( i ); } uart_send_byte( 13 ); // carriage return uart_send_byte( 10 ); // line feed __bis_SR_register( LPM3_bits + GIE ); } } // This handler runs when Switch S2 is pressed. void Port_1 (void) __attribute__((interrupt(PORT1_VECTOR))); void Port_1(void) { P1IFG &= ~BIT3; // clear interrupt flag __bic_SR_register_on_exit( LPM3_bits ); // wake up the main program }
To build this and send it to a Launchpad card:
$ msp430-gcc -mmcu=msp430g2553 -o main.elf main.c $ mspdebug rf2500 "prog main.elf"
To test the program, I connected the TXD and RXD pins from the msp430g2553 to a TTL level USB serial converter, which enumerated on my system as /dev/ttyUSB0. I started Minicom via the following command (if you don’t have permission for the serial ports, you may need to prefix the command with sudo):
$ minicom -D /dev/ttyUSB
Make sure that hardware flow control is off and the serial settings are at 9600 bps, 8 data bits, no parity, and 1 stop bit. Waking the main routine by pressing S2 on the Launchpad card should cause the alphabet to appear in Minicom.
2. Interrupt driven USCI on the MSP430
Polling code is easy, but idling in a loop is a drain on power that could be saved if the chip were put into low power mode. The following program communicates with the computer at the same 9600 bits per second as the previous program, but rather than loop until the USCI interface is ready to send each character this program sleeps almost all the time. When it’s time to send a character, an interrupt briefly wakes the program which loads the next character and then goes back to sleep.
#include "msp430g2553.h" char *hello = "Hello, world.\r\n"; // a 15 character message int tx_count = 0; void uart_init( void ) { /** * 1. Set UCSWRST * 2. Initialize all USCI registers * 3. Configure ports * 4. Clear UCSWRST * 5. Enable interrupts via UCxRXIE and/or UCxTXIE (optional) */ // (1) Set state machine to the reset state UCA0CTL1 = UCSWRST; // (2) Initialize USCI registers UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK UCA0BR0 = 104; // 1MHz/9600 = 104 UCA0BR1 = 0x00; // UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 // (3) Configure ports P1SEL = BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD P1SEL2= BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD // (4) Clear UCSWRST flag UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine** // (5) Enable interrupts } void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog timer // The UART settings used depend on a good 1MHz clock BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1DIR &= ~BIT3; // P1.3 is an input pin P1IE |= BIT3; // Switch S2 triggers an interrupt P1IES |= BIT3; // on falling edge uart_init(); // Sleep. All of the action happens in the interrupt handlers. while( 1 ) { __bis_SR_register( LPM3_bits + GIE ); } } // This handler runs when Switch S2 is pressed. void Port_1 (void) __attribute__((interrupt(PORT1_VECTOR))); void Port_1(void) { P1IFG &= ~BIT3; // clear the interrupt flag IE2 |= UCA0TXIE; // enable interrupts to transfer via the other handler P1IE &= ~BIT3; // disable this button (for now) } // This routine does the sending. An interrupt is triggered each time // the UART is ready to transmit the next character. void USCI_A_tx (void) __attribute__((interrupt(USCIAB0TX_VECTOR))); void USCI_A_tx (void) { UCA0TXBUF = hello[ tx_count++ ]; // Load the next character // and increment the count. // After the 15 characters of the message are sent, we're done. if( tx_count >= 15 ) { IE2 &= ~UCA0TXIE; // disable this routine so it won't be called again P1IE |= BIT3; // re-enable the S2 switch interrupt tx_count = 0; // reset the count for the next transmission } }
Building and testing are exactly the same as the previous program.
3. Analog-to-Digital conversions using Vcc as a voltage reference
Ultimately, my goal with this chip is to sample some voltages and then relay that information to a computer (via the UART, which we now have running) and an LCD display. Now that the chip can talk to a computer we can start experimenting with the analog-to-digital converter.
For experimenting, I set up a voltage divider with a couple of resistors to be my sample voltage. I measured the voltage with a voltmeter at around 0.86 volts which should be suitable for sampling against either the internal 1.5V reference voltage or 2.5V reference voltage of the MSP430. Since I’ll be using analog input A5 (or pin P1.5 of the microcontroller) as one of my inputs in my actual project, I used that input for my experiments as well.
This first program uses Vcc as the reference voltage, which I verified on my Launchpad to be about 3.56V. The UART code is essentially unchanged from before.
Like USCI, the setting up for ADC is generally a lengthy process and the actual sampling is easier. There are many things to specify including 1) the reference voltage, 2) a clock (and speed) to drive the sampling, 3) the number a clock cycles for a sample, 4) how a new sample will be triggered, and 5) whether only a single sample or multiple samples will be done. When it’s time to sample, you basically just tell the processor to do it and wait for the result.
For some applications these values are more critical. For my application timing won’t be critical because the voltages won’t be changing quickly. I just tried to pick reasonable values for this test program.
#include "msp430g2553.h" char hello[] = " \r\n"; int tx_count = 0; void uart_init( void ) { /** * 1. Set UCSWRST * 2. Initialize all USCI registers * 3. Configure ports * 4. Clear UCSWRST * 5. Enable interrupts via UCxRXIE and/or UCxTXIE */ // (1) Set state machine to the reset state UCA0CTL1 = UCSWRST; // (2) Initialize USCI registers UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK UCA0BR0 = 104; // 1MHz/9600 = 104 UCA0BR1 = 0x00; // from User Guide table 15-4 UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 // (3) Configure ports P1SEL = BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD P1SEL2= BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD // (4) Clear UCSWRST flag UCA0CTL1 &= ~UCSWRST; // Initialize USCI state machine } void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog timer // UART settings depend on an accurate 1 MHz clock. BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1DIR &= ~BIT3; // Set P1.3 to input pin P1IE |= BIT3; // P1.3 (S2) triggers an interrupt P1IES |= BIT3; // on falling edge // Initialize the UART uart_init(); // Initialize the ADC10 ADC10CTL0 = SREF_0 | ADC10SHT_2 | ADC10ON; // Vcc reference, 16 clocks, on // input channel 5, trigger on ADC10SC bit, no clock division, // internal ADC clock, single channel single conversion ADC10CTL1 = INCH_5 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_0; ADC10AE0 = BIT5; // enable analog input on A5 ADC10CTL0 |= ENC; // enable conversions // Go to sleep. We'll do an ADC and send data when the Switch S2 is pressed. while( 1 ) { __bis_SR_register( LPM3_bits + GIE ); ADC10CTL0 |= ADC10SC; // trigger the conversion while( ADC10CTL1 & ADC10BUSY ) ; // loop until done itoa( ADC10MEM, &hello, 10 ); // convert to a string IE2 |= UCA0TXIE; // enable interrupts to start the transfer } } // Switch S2 was pressed. Get things started. void Port_1 (void) __attribute__((interrupt(PORT1_VECTOR))); void Port_1(void) { int i; P1IFG &= ~BIT3; // clear interrupt flag P1IE &= ~BIT3; // disable this button // blank out any previous values for( i = 0; i < 10; i++ ) hello[i] = ' '; __bic_SR_register_on_exit( LPM3_bits ); // wake up main program } // This routine does the sending. An interrupt is triggered each time // the UART is ready to transmit the next character. void USCI_A_tx (void) __attribute__((interrupt(USCIAB0TX_VECTOR))); void USCI_A_tx (void) { UCA0TXBUF = hello[ tx_count++ ]; // Load the next character // and increment the count. // After the 15 characters of the message are sent, we're done. if( tx_count >= 15 ) { IE2 &= ~UCA0TXIE; // disable this routine so it won't be called again P1IE |= BIT3; // re-enable the S2 switch interrupt tx_count = 0; // reset the count for the next transmission } }
As usual, I built and installed the program with the same commands, and I monitored the output with Minicom.
$ msp430-gcc -mmcu=msp430g2553 -o main.elf main.c $ mspdebug rf2500 "prog main.elf"
Each time you press Switch S2, it takes another sample, converts the (integer) value to a string and sends it to the computer. In my case the number reported was 248. Usually. Sometimes it was 249 and every once in a while it was slightly smaller or larger. But the most common value was 248.
That is to be expected. Since this is a 10 bit ADC unit, the values returned will be between 0 (when the input is near 0 volts) and 2^10 - 1 (when the input is near the voltage reference or higher). To convert back to a voltage, use the formula sample/1024.0 * reference. In this case, 248/1024.0 * 3.55 = 0.86 which agrees with my multimeter.
4. Analog-to-Digital conversions against a 1.5V reference
Converting against one of the internal reference voltages is as simple as initializing things differently. The next program only differs from the previous in one line, the line where ADC10CTL0 is set up. Instead of setting bit SREF_0, we set two new bits SREF_1 and REFON. One selects the internal reference voltage instead of Vcc and the other turns the reference voltage on.
#include "msp430g2553.h" char hello[] = " \r\n"; int tx_count = 0; void uart_init( void ) { /** * 1. Set UCSWRST * 2. Initialize all USCI registers * 3. Configure ports * 4. Clear UCSWRST * 5. Enable interrupts via UCxRXIE and/or UCxTXIE */ // (1) Set state machine to the reset state UCA0CTL1 = UCSWRST; // (2) Initialize USCI registers UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK UCA0BR0 = 104; // 1MHz/9600 = 104 UCA0BR1 = 0x00; // UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 // (3) Configure ports P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD P1SEL2= BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD // (4) Clear UCSWRST flag UCA0CTL1 &= ~UCSWRST; // Initialize USCI state machine } void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog timer // UART settings depend on an accurate 1 MHz clock. BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1DIR &= ~BIT3; // Set P1.3 to input pin P1IE |= BIT3; // P1.3 (S2) triggers an interrupt P1IES |= BIT3; // on falling edge // Initialize the UART uart_init(); // Initialize the ADC10 ADC10CTL0 = SREF_1 | REFON | ADC10SHT_2 | ADC10ON; // 1.5V reference, 16 clocks, on // input channel 5, trigger on ADC10SC bit, no clock division, // internal ADC clock, single channel single conversion ADC10CTL1 = INCH_5 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_0; ADC10AE0 = BIT5; // enable analog input on A5 ADC10CTL0 |= ENC; // enable conversions // Go to sleep. We'll do an ADC and send data when the Switch S2 is pressed. while( 1 ) { __bis_SR_register( LPM3_bits + GIE ); ADC10CTL0 |= ADC10SC; // trigger the conversion while( ADC10CTL1 & ADC10BUSY ) ; // loop until done itoa( ADC10MEM, &hello, 10 ); // convert the answer to a string IE2 |= UCA0TXIE; // enable interrupts to start the transfer via UART } } // Switch S2 was pressed. Get things started. void Port_1 (void) __attribute__((interrupt(PORT1_VECTOR))); void Port_1(void) { int i; P1IFG &= ~BIT3; // clear interrupt flag P1IE &= ~BIT3; // disable this button (for now) // blank out any previous values for( i = 0; i < 10; i++ ) hello[i] = ' '; __bic_SR_register_on_exit( LPM3_bits ); // wake up main program } // This routine does the sending. An interrupt is triggered each time // the UART is ready to transmit the next character. void USCI_A_tx (void) __attribute__((interrupt(USCIAB0TX_VECTOR))); void USCI_A_tx (void) { UCA0TXBUF = hello[ tx_count++ ]; // Load the next character // and increment the count. // After the 15 characters of the message are sent, we're done. if( tx_count >= 15 ) { IE2 &= ~UCA0TXIE; // disable this routine so it won't be called again P1IE |= BIT3; // re-enable the S2 switch interrupt tx_count = 0; // reset the count for the next transmission } }
When this program runs, it returns sample values of 586 and 587 to the computer (sometimes one, sometimes the other, and also other nearby values on occasion). Using the same formula sample/1024.0 * reference, you can check that these are reasonable values for 0.86V measured against a 1.5V reference.
5. Analog-to-Digital conversions against a 2.5V reference
Again, this code is a one line change from the previous code. In this case, we’ve added the flag REF2_5V to specify the 2.5V internal reference in lieu of the 1.5V reference.
#include "msp430g2553.h" char hello[] = " \r\n"; int tx_count = 0; void uart_init( void ) { /** * 1. Set UCSWRST * 2. Initialize all USCI registers * 3. Configure ports * 4. Clear UCSWRST * 5. Enable interrupts via UCxRXIE and/or UCxTXIE */ // (1) Set state machine to the reset state UCA0CTL1 = UCSWRST; // (2) Initialize USCI registers UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK UCA0BR0 = 104; // 1MHz/9600 =104 UCA0BR1 = 0x00; // from User Guide table 15-4 UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 // (3) Configure ports P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD P1SEL2= BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD // (4) Clear UCSWRST flag UCA0CTL1 &= ~UCSWRST; // Initialize USCI state machine } void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog timer // UART settings depend on an accurate 1 MHz clock. BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1DIR &= ~BIT3; // Set P1.3 to input pin P1IE |= BIT3; // P1.3 (S2) triggers an interrupt P1IES |= BIT3; // on falling edge // Initialize the UART uart_init(); // Initialize the ADC10 ADC10CTL0 = SREF_1 | REF2_5V | REFON | ADC10SHT_2 | ADC10ON; // 2.5V reference, 16 clocks, on // input channel 5, trigger on ADC10SC bit, no clock division, // internal ADC clock, single channel single conversion ADC10CTL1 = INCH_5 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_0; ADC10AE0 = BIT5; // enable analog input on A5 ADC10CTL0 |= ENC; // enable conversions // Go to sleep. We'll do an ADC and send data when the switch S2 is pressed. while( 1 ) { __bis_SR_register( LPM3_bits + GIE ); ADC10CTL0 |= ADC10SC; // trigger the conversion while( ADC10CTL1 & ADC10BUSY ) ; // loop until done itoa( ADC10MEM, &hello, 10 ); // convert to a string IE2 |= UCA0TXIE; // enable interrupts to start the transfer } } // Switch S2 was pressed. Get things started. void Port_1 (void) __attribute__((interrupt(PORT1_VECTOR))); void Port_1(void) { int i; P1IFG &= ~BIT3; // clear interrupt flag P1IE &= ~BIT3; // disable this button // blank out any previous values for( i = 0; i < 10; i++ ) hello[i] = ' '; __bic_SR_register_on_exit( LPM3_bits ); // wake up main program } // This routine does the sending. An interrupt is triggered each time // the UART is ready to transmit the next character. void USCI_A_tx (void) __attribute__((interrupt(USCIAB0TX_VECTOR))); void USCI_A_tx (void) { UCA0TXBUF = hello[ tx_count++ ]; // Load the next character // and increment the count. // After the 15 characters of the message are sent, we're done. if( tx_count >= 15 ) { IE2 &= ~UCA0TXIE; // disable this routine so it won't be called again P1IE |= BIT3; // re-enable the S2 switch interrupt tx_count = 0; // reset the count for the next transmission } }
This program returns sample values around 355, which you can verify is approximately correct for a 0.86V source measured against a 2.5V reference.
6. Doing a succession of ADC conversions with DMA
In my power supply I am going to want to monitor four voltages. Because other pins will be handling UART and I2C communication, I’ll want to measure the voltages connected to the inputs A5,A4,A3,A0.
I could do this by hand, by changing the input channel in ADC10DTC1 from INCH_5 to the other channels I want to sample, but the MSP430 has a mechanism for sampling from multiple inputs and returning all of the values via DMA (direct memory access).
The following program incorporates several changes to make this work. It includes a new array, adc_result[] which will hold the result of our sequence of ADC conversions. I’ve filled it initially with junk data (the numbers 7 through 12) for this experiment so we’ll be able to see that the conversions have been successful.
The UART code is largely unchanged, though our message length has been extended to 42 characters in order to return all of the values at once.
When setting up ADC10CTL0 there is a new flag, MSC which tells the unit we want multiple sample conversions. In ADC10CTL1 the flag CONSEQ_1 tells how we want the multiple samples to be done, we want to count down from the selected input channel (i.e. INCH_5 then INCH_4 and so on) and sample each channel one time.
The register ADC10DTC1 sets the number of conversions we want to do. For example, if we set this to 2, it would sample input channel 5 then input channel 4 and stop (i.e. A5 and A4). We want four channels A5, A4, A3, and A0, but there is no way to specify non-consecutive channels, so our program selects 6 conversions, from input channel 5 all the way down to input channel 0. We’ll get two conversions that we don’t need (A2 and A1), but we can just throw those values away.
Perhaps the most confusing register is ADC10AE0. You would naturally read this as "analog enable", and at first I tried setting BIT5, BIT4, BIT3, and BIT0 to enable just the inputs I actually wanted to sample. I thought that I could set ADC10DTC1=4 and skip the two inputs I don’t care about. This is not actually what ADC10AE0 does. It doesn’t really "enable" a pin as an analog pin. What it really does is disable all of the digital functions of the pin. You can always sample a pin with the ADC unit, no matter the setting of this register. The ADC is always connected to each input, always. However, if you are using a pin as an analog input and won’t need any digital functions, you can set the corresponding bit in this register to save power.
In our program, notice that I didn’t set BIT3 in ADC10AE0. I need that pin to run Switch S2 while we experiment, and that function stops working if you disable the digital function of the pin. (In my bench power supply I won’t need a Switch S2 and I’ll be free to set this bit then.)
The only other major change is that ADC10SA holds the start address where samples will be saved, and we give it the address of the array we created. Each sample is a two byte value (an unsigned integer between 0 and 1023 in this case).
The ADC transfers samples to memory in the order they are taken (counting down from input channel 5 in our program). That means A5 will be stored in adc_result[0], with A4 in adc_result[1] and so on. This might be the reverse of what you would have expected. |
#include "msp430g2553.h" char hello[] = " \r\n \r\n \r\n \r\n \r\n \r\n"; int tx_count = 0; unsigned int adc_result[6] = { 7, 8, 9, 10, 11, 12 }; void uart_init( void ) { /** * 1. Set UCSWRST * 2. Initialize all USCI registers * 3. Configure ports * 4. Clear UCSWRST * 5. Enable interrupts via UCxRXIE and/or UCxTXIE */ // (1) Set state machine to the reset state UCA0CTL1 = UCSWRST; // (2) Initialize USCI registers UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK UCA0BR0 = 104; // 1MHz/9600 =104 UCA0BR1 = 0x00; // from User Guide table 15-4 UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 // (3) Configure ports P1SEL = BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD P1SEL2= BIT1 + BIT2; // P1.1 = RXD, P1.2=TXD // (4) Clear UCSWRST flag UCA0CTL1 &= ~UCSWRST; // Initialize USCI state machine } void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog timer // UART settings depend on an accurate 1 MHz clock. BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1DIR &= ~BIT3; // Set P1.3 to input pin P1IE |= BIT3; // P1.3 (S2) triggers an interrupt P1IES |= BIT3; // on falling edge // Initialize the UART uart_init(); // Initialize the ADC10 using // 2.5V reference, 16 clocks, on, multiple samples ADC10CTL0 = SREF_1 | REF2_5V | REFON | ADC10SHT_2 | ADC10ON | MSC; // input channel 5, trigger on ADC10SC bit, no clock division, // internal ADC clock, sequence of conversions counting down from A5 ADC10CTL1 = INCH_5 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_1; // We want samples of A5, A4, A3, and A0 but you have to take all 6. ADC10DTC1 = 6; // This is a funny register. It's called "analog enable" but really // what it does is "digital disable". You can always sample from a pin, // and this doesn't change that. What it does is disconnect any digital // activity from the pin, so it can only work for ADC. Saves power. ADC10AE0 = BIT5 + BIT4 + BIT0; // (all but A3 which we need for switch S2) // Go to sleep. We'll do an ADC and send data when the switch S2 is pressed. while( 1 ) { __bis_SR_register( LPM3_bits + GIE ); ADC10SA = adc_result; // destination address for DMA ADC10CTL0 |= ENC + ADC10SC; // trigger the conversion while( ADC10CTL1 & ADC10BUSY ) ; itoa( adc_result[5], hello, 10 ); // convert A0 to a string itoa( adc_result[4], hello+7, 10 ); // convert A1 to a string itoa( adc_result[3], hello+14, 10 ); // convert A2 to a string itoa( adc_result[2], hello+21, 10 ); // convert A3 to a string itoa( adc_result[1], hello+28, 10 ); // convert A4 to a string itoa( adc_result[0], hello+35, 10 ); // convert A5 to a string IE2 |= UCA0TXIE; // enable interrupts to start the UART } } // Switch S2 was pressed. Get things started. void Port_1 (void) __attribute__((interrupt(PORT1_VECTOR))); void Port_1(void) { int i, j; P1IFG &= ~BIT3; // clear interrupt flag P1IE &= ~BIT3; // disable this button // blank out any previous values for( i = 0; i < 6 ; i++ ) for( j = 0; j < 5; j++ ) hello[i*7 + j] = ' '; __bic_SR_register_on_exit( LPM3_bits ); // wake up main program } // This routine does the sending. An interrupt is triggered each time // the UART is ready to transmit the next character. void USCI_A_tx (void) __attribute__((interrupt(USCIAB0TX_VECTOR))); void USCI_A_tx (void) { UCA0TXBUF = hello[ tx_count++ ]; // Load the next character // and increment the count. // After the 15 characters of the message are sent, we're done. if( tx_count >= 15 ) { IE2 &= ~UCA0TXIE; // disable this routine so it won't be called again P1IE |= BIT3; // re-enable the S2 switch interrupt tx_count = 0; // reset the count for the next transmission } }
For inputs, I hooked A0 to another voltage divider (at about 2.36V). I hooked A4 to Vcc, and when Switch S2 is pressed A3 will be grounded. Pressing the S2 switch gave output something like:
973 1023 1023 0 1023 356
From top to bottom, these are the values of A0 through A5. You can check that A0 gives approximately the correct value for 2.36V. Inputs A1 and A2 are both high because those pins are running the UART connection, and when a serial connection is idle the line levels are set to logical 1 (Vcc). Input A3 is 0 because the S2 Switch is grounded. Input A4 is high because it is connected to Vcc, and the value of A5 is consistent with 0.86V.