Search Microcontrollers

Showing posts with label MSP430G2. Show all posts
Showing posts with label MSP430G2. Show all posts

Thursday, November 1, 2012

MSP430G2 - ADC / 2 - sampling an audio signal

I thought it would have been fun to sample an audio signal using the launchpad.
Before starting : The MSP430G2 is not designed with audio sampling in mind, so if you want to develop some good digital recorder or audio sampling device, you should probably look for more suitable devices.
... still, it can be done.

One issue is the limited amount of RAM in the mcu, which would prevent us to buffer and process the audio data internally (i.e. with an FFT).
The only hope we have is to get the data from the ADC and send it as fast as possible to the serial port, then use it in the pc itself.
You can achieve the same task, much easily and better using the internal sound card of your pc, but that would not be fun at all.

First thing I prepared a stereo cable to get the audio signal out of my computers headphone connection.
I used an old internal CD cable to which I soldered a stereo jack.


Then I checked I could get signals from both channels, using my scope (yup, to play with audio signals, the scope is quite handy).


Since we are going to feed this signal to our ADC converter, we need to ensure that its values are within the limits defined by Vref- and Vref+, meaning the two reference voltages used by the converter to define the minimum and maximum values to be sampled.
Most ADCs, including the MSP430G2 ADC10, can sample only positive signals (Vref- >+0) with a maximum value normally equal to their Vcc (3.3V for the launchpad).

Audio signals are obviously AC signals and typically they have their offset at 0V (you need to check that, it was the case for me, check the image above).
[The offset voltage is the voltage you obtain when you have the jack connected and no sound is played. When sound is played the offset can be eyeballed as the median voltage level]
This means that typically half of your audio signal has negative values, which would not work with the ADC.
We need to add a positive offset in a way that the incoming signal is never negative.

The other important thing is the amplitude of the signal (which you change with the volume settings).
I saw that most of the audio coming out from a youtube video had an amplitude of about 800mV, but pumping up the volume and playing some loud music showed a higher amplitude, reaching about 2V.

The ADC10 can use different Vref values, to make things easier we will use Vref- = 0V (GND) and we need to chose from 1.5V, 2.5V, Vcc (3.3V) for Vref+.

The signal amplitude we can take, assuming we can offset it exactly in the middle of our range is equal to Vref+ - Vref- , so having Vref- = 0V, the amplitude is equal to Vref+.
I would go for 2.5V because it is a bit higher than input signal amplitude, but not too much.

As a general rule, you normally want to amplify (or attenuate) the signal to make sure it fits nicely in your ADC band.

The reason why you want to  match your reference as close as possible is that, when sampling digitally, you divide your reference in a finite number of levels (1024 for a 10 bit ADC).
If the incoming signal has an amplitude which is covering just a small portion of your sampling band, then you are wasting sampling resolution.

Audio signal are pretty much symmetric in respect of their offset, so it's in general a good idea to move the offset in the middle of the sampling range, in our case to 2.5 / 2 = 1.25 V.
The typical way to achieve this is via a simple circuit based on an op-amp, but for this example (remember, this is an experiment, don't design devices in this way, it's probably not  a good idea!) I decided I wanted something simpler.

I just needed something capable to deliver a clean 1.25V and stick it in series with my signal.
That sounds like a fully charged rechargeable AA battery to me :)


A battery holder and a couple of crocodile contacts helped to put the battery in series with the signal before entering the scope probe (easier than dealing with op-amps, right?).
I checked with the scope setting the cursors to my Vref values (0 and 2.5V), this is what I obtained :


Eureka! This looks definitely a signal that could be handled by the ADC10.

Let the fun begin

It's time to start preparing our application.
The idea is to sample both channels (stereo!!) and eventually transfer the sampled data via UART.
The msp4302553 has 8 external Analog inputs mapped on port P1 (P1.0 to P1.7), we need to select two suitable channels in a way that they do not overlap with other functions we might need.
I will use the serial port, most likely UART, so I will steer clear from the pins used by that functionality (even if for this test I am planning to use the onboard FTDI chip that sends the uart data to the usb connection).
The 2553 datasheet has it all : our A3 / A4 channels (P1.3 , P1.4) look like good candidates.

Also in the datasheet we find  that there is no real need to set the multiplex values (P1SEL, P1SEL2) or the P1DIR register when using pins as ADC channels, because the ADC10 will set them for us (everybody say with me : "Thanks mister ADC10!") using the  ADC10AE0 register.


The ADC10 can do some interesting things when sampling multiple channels :
It can automatically sample a sequence of channels (that is actually quite cool for our application).

When sampling a sequence of channels, we need to specify the highest channel  we want to sample and the mcu will start a loop in which it will sample all the channels down to A0,
from the user guide :
"A sequence of channels is sampled and converted once. The sequence begins with the channel selected by INCHx and decrements to channel A0. Each ADC result is written to ADC10MEM. The sequence stops
after conversion of channel A0."

Fortunately, despite what stated in the user guide, it seems it is possible to set how many channels must be sampled, the DTC (a sort of dma controller, dedicated to the ADC10) can transfer the values to a defined memory area.


Time to write some code.


#include <msp430g2553.h>

unsigned int sampling;
unsigned int buffer[2];

void clockConfig()
{
 // configure the CPU clock (MCLK)
 // to run from DCO @ 16MHz and SMCLK = DCO / 4
 BCSCTL1 = CALBC1_16MHZ; // Set DCO
 DCOCTL = CALDCO_16MHZ;
 BCSCTL2= DIVS_2 + DIVM_0; // divider=4 for SMCLK and 1 for MCLK
}

void adcConfig()
{

  // ADC10SSEL_0 :  ADC10OSC
  // ADC10DIV_0 : ADC10 Clock Divider Select 0. Roughly 5MHz
  // INCH_4 : highest channel we are going to sample
  // CONSEQ_1 :  Sequence of channels
  ADC10CTL1 = ADC10SSEL_0 + ADC10DIV_0 + INCH_4 +  CONSEQ_1; //
  // SREF_1 : VR+ = VREF+ and VR- = AVSS
  // ADC10SHT_0 : Sample & Hold = 4 x ADC10CLKs
  // REF2_5V : ADC10 Ref 0:1.5V / 1:2.5V;
  ADC10CTL0 =  SREF_1 + REF2_5V + REFON + ADC10SHT_0 + MSC + ADC10ON + ADC10IE;
  ADC10AE0 |= BIT3 + BIT4;  // adc option for channels 3 and 4
  ADC10DTC1 = 0x02; // number of channels to be sampled
  __delay_cycles(1000);  // Wait for ADC Ref to settle
}

// temporary interrupt routine to check that the ADC sequence is complete
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{
  sampling = 0;
}

void main(void)
{
 WDTCTL = WDTPW + WDTHOLD;
 clockConfig();
 adcConfig();
 __bis_SR_register(GIE);  // Enable interrupts
 while (1)
 {
  ADC10CTL0 &= ~ENC;
  while (ADC10CTL1 & BUSY);               // Wait if ADC10 core is active
  sampling = 1;
  ADC10SA = (unsigned int)&buffer;        // Data buffer start
  ADC10CTL0 |= ENC + ADC10SC;             // Sampling and conversion start
  while(sampling>0); // wait until the sampling sequence is complete
 }
}


At this point you should refer to the previous post in which I introduced the ADC10, if you did not read it before.

Basically I opted to use the internal ADC oscillator at its maximum frequency.
For Vref I used VRef+ = internal reference and VRef- = GND
The internal reference is configured at 2.5V (default is 1.5V) and the sample & hold time is the shortest possible (4 cycle of ADC10CLK).
The MSC bit is set, that tells the ADC to keep converting data once the encoding request is triggered, then DTC controller will post it to the buffer starting at the address of the buffer variable (ADC10SA =(unsigned int)&buffer; )
It seems that the buffer start address must be set each time before starting a new conversion.

There are four operating modes selected with the CONSEQ constants :

#define CONSEQ_0               (0*2u)         /* Single channel single conversion */
#define CONSEQ_1               (1*2u)         /* Sequence of channels */
#define CONSEQ_2               (2*2u)         /* Repeat single channel */
#define CONSEQ_3               (3*2u)         /* Repeat sequence of channels */

I chose to sample a single sequence of channels, the DTC will drop the data in my two 16 bit integer buffer.

I also use a sampling variable to check if the sequence is complete or not, this is a simple temporary solution, in future implementations we might simply use the interrupt routine to send the data via uart.

That's about it for today, I debugged the program and -surprise surprise- my buffer is actually filled with values that vary at each loop, indicating that the DTC is sending it samples.
I also notice that with no audio incoming my channels they are at values around 510, which matches pretty closely my ideal offset (1024/2 = 512).

I might develop this experiment further and actually send some data to the pc... but that's going to be another post.  

Another thing to look into is to test if we can get away without the interrupt and just poll the ADC BUSY bit, it might actually be interesting to maximize the data transfer to the pc.


Sunday, October 28, 2012

MSP430G2 - the ADC10 peripheral /1

In the previous post I illustrated a few basic concepts about Analog to Digital Converters.
Having them integrated into MCUs makes it really easier to use them, we are going to play a bit with the MSP430G2553 ADC10 converter.

The two main control registers to drive the ADC10 are ADC10CTL0 and ADC10CTL1.
Using them we can set the reference voltages, which channel to be sampled, we can turn on and off the reference voltage and the peripheral itself.

The MSP430G2 family is designed to be a low power solution, this is why the peripherals and features that may potentially use some energy are turned off by default.

Finally the sampled result can be easily recovered from the ADC10MEM register :
int mySensor = ADC10MEM;

Sample & Hold and conversion

During the sampling process, I explained in the previous post, a reference voltage is V(t) is compared with the voltage coming from the signal.
If the signal fluctuates in this process, it might be impossible to compare it with the reference.
For this reason a "sample and hold" functionality is implemented, you can see some details here.

This means that the value of the signal to be sampled is maintained constant during the conversion.

    from wikipedia

This time is defined by the SHS (Sample and Hold Source) clock which, in the MSP430 can be one of the following :


00   = ADC10SC  (ADC10 internal oscillator, about 5MHz and can be  divided from 1 to 8)
01   =  Timer_A.OUT1
10   = Timer_A.OUT0
11   = Timer_A.OUT2


In this way we basically define the maximum sampling rate which is the result of the sampling time, being the sum of the Sample and Hold phase plus the conversion phase.

from the TI MSP430 User Guide

The S&H phase is software selectable  (in ADC10CTL0) in 4,8,16 or 64 ADC10CLK cycles, a higher value would be used to reduce high frequency noise.
The conversion part is then executed in 13 ADC10CLK cycles (thanks to a series of approximations performed internally).

Playing a bit with numbers, we can verify that, at a minimum we need 4 + 13 = 17 cycles.
Assuming the ADC10CLK is the 5MHz (note : we should not expect it to be EXACTLY 5MHz, it's a digital oscillator and it might have some tolerance) Internal source

1 cycle = 1 / 5.000.000 s = 200ns

The sampling time is then 17 * 200ns = 3.5us
The sampling time is 1 / 3.5us = roughly 294 Ksps (Kilo Samples Per Second)
Data then needs about 1 or 2 MCLK cycles to be made available in the ADC10MEM register (it uses an internal DMA), so the actual rate is slightly lower and it depends on the CPU frequency.

The data sheet claims a sampling rate exceeding 200 Ksps, which is in line with what we calculated and I believe is pretty good for a 16MHz mcu.

Reference Voltages

The ADC will return 0 if the signal is lower than or equal to the Vref- voltage and will return 1023 if it is higher than or equal to Vref+.

The ADC10 has different options when it comes to Reference voltages, it can use internal precisely  generated voltages (1.5V or 2.5V) , it can use Vcc as Vref+ and Vss (Ground) as Vref- or it can get them from two external pins.
All these parameters are controlled via the ADC10CTL0 register.

In the user guide we find :


SREFx Bits 15-13 Select reference.
SREF_0 000 VR+ = VCC and VR- = VSS
SREF_1 001 VR+ = VREF+ and VR- = VSS
SREF_2 010 VR+ = VeREF+ and VR- = VSS. Devices with VeREF+ only.
SREF_3 011 VR+ = Buffered VeREF+ and VR- = VSS. Devices with VeREF+ pin only.
SREF_4 100 VR+ = VCC and VR- = VREF-/ VeREF-. Devices with VeREF- pin only.
SREF_5 101 VR+ = VREF+ and VR- = VREF-/ VeREF-. Devices with VeREF+/- pins only.
SREF_6 110 VR+ = VeREF+ and VR- = VREF-/ VeREF-. Devices with VeREF+/- pins only.
SREF_7 111 VR+ = Buffered VeREF+ and VR- = VREF-/ VeREF-. Devices with VeREF+/- pins only.


The constants SREF_0 to SREF_7 are conveniently defined in the standard .h file.
In the same file we also find the other constants needed to enable the internal reference and to set it to 1.5 or 2.5 V


REF2_5V  Reference-generator voltage. REFON must also be set.
0 1.5 V
1 2.5 V
REFON Reference generator on
0 Reference off
1 Reference on


There anyways restrictions on the voltages that can be applied to the analog input (should be between 0 and Vcc) and to the eventual external references :
Vref- (specifically for the 2553, check the DS for other devices) should be between 0 and 1.2V
Vref+ (same considerations) should be between 1.4V and Vcc

This said, if you are planning to sample a higher voltage (I tried with the mains ; 250V AC) you need to use a voltage divider (with offset regulation for AC signals) and to make sure the output of your divider is always in the expected range (check with a scope if you can).

If you don't need to sample with a high frequency and you are using an internal reference, what you may want to do is to turn on and off the internal Vref to reduce power consumption.
There is also an option to burst" it so it turns off and on as needed by the ADC, however you should be aware of the fact that the internal Vref  takes a small amount of time to settle, and the sampling can happen only when it is stable.
tref (the time Vref internal needs to settle at 99.9% of its value) is 30us.

Finally, the peripheral can fire an interrupt once the conversion is done, so it's common to start the process, enter in Low Power Mode and then wake up receiving the interrupt, get the value from ADC10MEM, do whatever needed with it and start the process again.

In the next post I will present some sample code using the ADC.


Analog to Digital Converters - 1

Analog to Digital converters are, in my opinion, one of the most fun peripheral to deal with when working with Microcontrollers.
Why?
Let's face it : most of the measurements you want to do in the real world involve an analog reading, normally a voltage or a resistance returned by some kind of sensor.

Like most of the mcus, the MSP430G2 has an ADC built in (warning : the ADC10 is not included in all the devices of the family, check the datasheet or the specs for the specific device you are dealing with).
I am going to use the MSP430G2553 supplied with the launchpad and that one definitely has an ADC10 onboard.

First of all, it's called ADC10 because it has 10 bits of resolution.
10 Bits is the typical resolution you can find in "cheap"devices, more advanced ones (such as the C2000 or Cortex M3) may have a 12bit adc instead.

What's the difference?
It's about resolution : a 12 bit device, having 2 bits more, achieves a four (2^2) times higher resolution than a 10 bit one.
It's a big difference, however in many (most?) cases it does not really matter.

Let's imagine you are sampling a temperature from a sensor such as the LM35DZ (cheap and common sensor).
This sensor increases it's output by 10mV for each Centigrade, at 0C it will provide 0V (which makes it really simple to use it with mcus).
However this sensor does not provide high accuracy since it's best accuracy is about +/- 0.5C, which can be translated into +/- 5mV.

A 10 bit adc with range between 0 and 3.3V has a resolution of 3.3 / 1024 V/bit =  3mV/bit which is already higher than the accuracy of the sensor itself, therefore a higher adc resolution would be useless.
However this setup would allow us to measure temperatures between 0 and 33C, which it might be ok for some applications, but not enough for others.
The LM35 sensor itself can read temperatures from -55C to 150C (depending of the exact model, check the datasheet) but this would generate voltages from -5.5V up to 15V.
If you apply such ranges to a 3.3V tolerant ADC, you are likely to burn it, so most people normally regard this a as a bad idea.
You can offset and "scale" the signal using a voltage divider (with 3 resistors in case you need to add the offset), but at that point, should you still aim for +/- 0.5C resolution, the 10 bits are not enough anymore.

The example with the LM35 was useful to add some context, but the issue is generic.
Imagine we have a sensor that measures a quantity X and outputs linearly a voltage V(X) = V0+kX.
The constant k is the voltage gain of the sensor, it is a constant if the sensor output is linear.
The range we want to measure is [Xmin,Xmax], which gives us DX = Xmax-Xmin.
Assuming we can exactly map Xmin to the lower reference of the ADC (i.e. 0V) and Xmax to the higher reference (i.e. 3.3V), and we know we want to obtain a specific resolution R, then we can calculate how many bits (minimum) we need to sample the signal.

Say DX = 100X and the needed resolution R is 0.01X, this means that the ADC must have 100X/0.01X = 10.000 "steps".
To get the minimum number of bits you can apply a log2(10.000) or simply compare with the resolution of finite number of bits :
8 bits -> 256
10 bits -> 1.024
12 bits -> 4.096
14 bits -> 16.384
...

in our case we would need a 14 bit ADC, not an easy requirement as very few of the mcu integrated ADCs can get that precision.
At the same time, when sampling with such accuracy, we might have additional issues such as the stability of the reference voltages, the accuracy of the resistors used in an eventual divider, electronic noise etc.
When higher precision and speed must be achieved, the common solution is to use dedicated ADC devices.

If now you feel frustrated because your mcu only supports a 10 bit sampling device, just think this : precision instruments such as Digital Oscilloscopes use 8 bit ADCs (but they are normally extremely fast and equipped with high precision input circuits).
How do they give all that flexibility and precision with 8 bit only?
They allow to set different scales by altering the offset of the signal and the parameters of the input voltage divider.

The other important parameter is the sampling frequency.
Why achieving high frequency and high resolution at the same time can be difficult?
That's related to the way the sampling happens, which I will try to explain in a simple case.
Digital devices only understand two states : on and off, so how do they convert a voltage to an on or off state?
They can compare the voltage with a reference one and say : if the reference is higher, then set to off, else set to on.
Now if the reference voltage V(t) varies starting from V- (lower reference) and linearly climbs to V+ (higher reference) and at the same time we start a timer that counts the number of cpu cycles, at a given point in time, the signal will cross the reference and the comparison will return "on".
At that point the timer count t gives us a measurement of the voltage used in the comparison V(t).
Practically, some more advanced techniques are used, but that's  pretty much the basic concept.
You understand then that the timer speed is affecting the resolution + sampling rate combination.
If we have a 10 bit resolution, the timer must be able to count up to 1024 when V(t) = V+.
Imagine the timer uses the same clock frequency as the CPU being 16MHz -> the maximum sampling rate would be 16/1024 MHz = 15KHz.
As I previously stated, some more advanced techniques (series of approximations etc) are used, allowing the adcs to achieve better performances, but you now probably understand the relation between cpu speed, sampling rate and resolution.

In the next post I am going to experiment with the ADC10 device of the MSP430G2553