Tuesday, September 14, 2010

A Simple ADC Example on the LaunchPad

I finally have had the time to write up this post. Hopefully in a few more weeks I will have more time and can keep up a steady stream of posts like I was doing before.

As promised in the last post, this post will discuss the basics of the ADC10 peripheral in the G2231 which comes with the LaunchPad. The code will allow the microcomputer to accept simple ASCII commands sent over the LaunchPad's UART from a computer, and act on those commands. Four commands are implemented for this post; there will be two more in the next post which will add streaming functionality to the code.

The Functionality

The first command tests the overall data throughput we can achieve with the microcomputer. I think this is very important since it allows us to see how well our device is performing, and allows us to determine how fast the ADC should sample in real time. The second command uses a built in voltage divider to measure VCC in relation to an internal reference. The third command measures the internal temperature sensor in relation to a different internal reference. The final command measures the voltage on an external pin, in this case A3 in relation to VCC. Note that this code does not continually sample the ADC channels but will only sample once per command. The next post will discuss continuous sampling of a single channel, which will allow us to build a simple (slow) oscilloscope using only the LaunchPad.

Computer Application

To accompany the code on the MSP430 I have written a simple
computer application using C# which will allow us to easily interface with the device so we do not have to play around with terminal programs. If any of your projects require a computer interface I highly recommend writing a custom application, I really hate fiddling around with terminal programs.

Note: It seems that there are some problems with the installer. I have not figured out what causes the application to work on some computers and not on others. Hopefully it is just my laptop that is having trouble. I recommend having the .NET 4.0 framework installed, even though I wrote the program for 3.5.

The software allows you to connect to any COM port at any baud rate. The baud rate for this post will be 9600, considering that is the maximum of the LaunchPad, but can be changed if you want to use something like an FTDI chip. Make sure that your LaunchPad is plugged in and the VCP is ready before you run the software, the program does not constantly check which ports are available.

The GUI is pretty self explanatory; if you have any questions, bug reports, or suggestions feel free to post. There is also some additional functionality built into the program which is currently disabled; I will provide a second version of the code for the next post when we start streaming data.

The Code

As always the code is available as a gist at http://gist.github.com/593606. If anyone knows how to force the embedded code to show a vertical scroll bar please let me know.

Going Through the Code

Most of this code builds off of previous posts so I will not be going over it in great detail. To read more about the UART please see the previous post. The code is broken down into multiple functions to increase readability. Receive(); is called when the main loop receives a new value from the software UART. This is not necessary, and might actually reduce the efficiency slightly since every function call requires an extra few assembly commands, but in my opinion it’s worth it since the code is more readable and much easier to modify.

After initializing all the registers the device goes into its main loop. So far the ADC is not initialized, only the software UART is set up. The main loop will determine which command was sent and then calls the necessary functions; the loop will also send any converted ADC values out using the software UART. As is true with most of the code I post, the chip goes into LPM0 when there is nothing to do. Before the CPU goes into low power mode it makes sure there are no flags waiting; this keeps the CPU from missing any events. If this was not done it is possible that a second event would be missed, since the CPU was enabled while it was currently in a loop.

The Receive() function will initiate different tasks depending on which command was sent to the computer. The first command which is handled is used to test the speed at which the UART can send data. It will send 256 values, from 0x0000 to 0x00FF, as fast as it can; this data will be received by the computer application and is tested for errors; if there are no errors the speed of the transmission is displayed. It is important to note that 512 bytes are sent total because each value is a 16 bit word. This was chosen for two reasons. First, the MSP430 is a 16 bit processor; second the ADC measurement can not fit in one byte. The next three commands measure an external analog channel (A3), the temperature, and VCC.

About the ADC

I will not be going into how ADCs work, but I will provide some basic theory pertaining to the one in the G2231. The G2231 has a 10 bit ADC and a multitude of channels available to be measured. A few of these channels are internal. They provide access to an integrated temperature sensor, a zero level calibration channel, and a VCC measurement channel.

First things first, the ADC is not some magical device that instantly determines the amplitude of a signal, the ADC conversion takes a certain number of clock cycles and has a limited sample rate determined by this number. Since we are using the LaunchPad's UART for this project, we are limited by the speed at which we can send the values to the computer and not the maximum sample rate. This post will not deal with consecutive measurements from the ADC, so speed is not a problem yet.

The last important theoretical topic I think is important to understand when using an ADC, is the reference voltage. In the ADC world, reference voltages are done differently for different types of converters. For example, what I am about to say does not pertain to the SD16 is the ADC in the F2013. I will probably make a future post about the SD16.

All ADCs need to have a voltage reference which the input voltage can be compared to. There not only is an upper voltage level which the signal is reference to, but also lower level voltage. For the ADC10, you can use the following as references.

  • VCC (upper)
  • VSS (lower)
  • VEREF- (lower)
  • VEREF+ (upper)
  • VREF- (lower)
  • VREF+ (upper)

There are two voltages which can be generated in the G2231, the 1.5V and the 2.5V reference. For this example we will use both the 1.5V and the 2.5V references, in addition to VCC, for all cases VSS will be used as the lower voltage reference.

The Two ADC Functions

The most basic of the ADC measurement functions, is the Single_Measure(chan) function. This does not use any internal references and will measure the channel 'chan' only once.

ADC10CTL0 &= ~ENC; // Disable ADC
ADC10CTL0 = ADC10SHT_3 + ADC10ON + ADC10IE; // 16 clock ticks, ADC On, enable ADC interrupt
ADC10CTL1 = ADC10SSEL_3 + chan; // Set 'chan', SMCLK
ADC10CTL0 |= ENC + ADC10SC; // Enable and start conversion

The first line is important since this might not be the first time the ADC is setup. Most values in CTL0 cannot be changed until the ENC bit is cleared; this is done to prevent unintentional parameter changes. Since this starts a single conversion we do not need to worry about setting up the timing correctly; I did not want to go into too much detail about this until the next post.

The next line determines how many clock cycles there are per conversion, turns on the ADC, and enables the ADC interrupt. Turning on and off the ADC10 is very important since the ADC will draw power as long as it is turned on. It is good programming practice to turn the device off when you are not using it.

The last two lines set the clock source, the ADC channel, and then start the ADC. This example uses the SMCLK and sets the channel to what was defined by ‘chan’. In order for the ADC to be started both the ENC and ADC10SC bits need to be set. See the Receive() function for how to call this ADC function.

Single_Measure_REF(chan, ref) is the second ADC function in this code. Like the first function this will only read one ADC value; the difference is that this function allows us to use one of the two internal references. See the Receive() function for how this function is used.
Other than the delay, there is only one different line between the first and the second ADC functions. TI recommends using a small delay after turning on an internal voltage reference to allow time for the voltage to settle; thus the small delay. Note: This only needs to be done when the reference is turned on.

ADC10CTL0 = SREF_1 + ADC10SHT_3 + REFON + ADC10ON + ref + ADC10IE;

This line of code does 3 things which the last function did not do. In addition to enabling the internal reference and setting whether the 2.5V or 1.5V reference is used, it specifies how the ADC uses these references. This line of code sets the ADC to use SREF_1, which uses VREF+ and VSS for upper and lower references respectively.

The Rest of the Code

The only other part of the code which you might not recognize is the ADC interrupt function. This function will let the main loop know that an ADC value is ready to be transmitted. The transmission is not done directly in the interrupt because one should minimize the amount of code which is present in interrupt routines. This is important since problems can arise when a new interrupt is thrown and the last interrupt has not finished executing yet.


I have decided to not include the source code for the application in this post, but I will be providing it for the next. For now the application is available as an installer only. See the readme provided with the application for a bit more information on how to use it and uninstall the program. If anyone knows how to release a truly standalone .exe using Visual Studio 2010, please let me know since I don’t like installers.

I hope that you find this code useful and are not too disappointed that I have not provided the code for continuous measurement just yet. The code is done, and I just need to test a few more things and write up the next post.

So if you have any questions, or feedback, please comment away. I feel as if this post is not as nicely written as my others, so if there is anything that is not clear please let me know.

Hope you enjoy the code and mini application!