Wednesday, October 8, 2014

Calibrating the MSP430 DCO

In this article I will give a brief explanation of the MSP340 DCO oscillator and I will provide a program to calibrate it to several frequencies.
This article is a rewrite from a previous article in spanish of my twin spanish blog AIM65.

DCO Description

The MSP340 familly of MCUs feature an internal digitally controlled RC oscillator (DCO). This oscillator generates a frequency that is controlled by three digital values:
  • RSELx  (0..15) Select a coarse frequency range
  • DCOx   (0..7)   Fine frequency selection for each range
  • MODx  (0..31) Frequency modulation between the selected fine value and the next one
The RSELx and DCOx values determine the oscillator frequency as is indicated in the following figure obtained from the MSP430x2xx family guide.

Frequency as function of RSELx and DCOx
The different frequencies you can select for each RSEL value have some overlap. For instance the RSEL=7 DCO=7 frequency could be higher that the one selected with RSEL=8 DCO=0.

The MODx value enables us a finer control of the average frequency. For each 32 clock cycles, (32-MOD) cycles will run at the frequency associated with the selected RSEL and DCO values and the other MOD cycles will run at the frequency associated with the same RSEL but with DCO+1 value.
As DCOx could only be between 0 and 7, the DCO value could not be 7 if we want to use the modulation.

The following figure shows the modulation pattern for different MOD values:

DCO modulation
It is important that the use of modulation introduces some Jitter in the clock signal. This is not important on some applications, but it could be a problem if, for instance, you need a low jitter periodic sampling.

Factory DCO calibration

The frequency associated to each RSEL and DCO value has a high tolerance. If you need an exact operation frequency you need to calibrate the oscillatror. As an example taken from the MSP430G2x21 datasheet, a setting of RSELx = 7, DCOx = 3 and MODx = 0 could yield frequency values between 0,8 MHz and 1,5 MHz. That is, you can have a 0,35 MHz error (23%) respect to the central value of 1,15MHz. 

To calibrate the DCO you must determine the RSEL, DCO and MOD values needed to generate a target frequency. A calibrated DCO gives a more exact frequency than an uncalibrated one, however it can also show some frequency drift in the +/- 6% range due to changes in Vdd and temperature. To minimize drifts, the best way is to calibrate the DCO at the same Vdd and operating temperature that will be used when the circuit is active.

All MSP430 MCUs are factory calibrated at least at 1MHz frequency. The three RSEL, DCO and MOD calibration values are stored on the internal flash memory. To set the calibrated frequency those values must be written to the DCOCTL (DCO and MOD) and BCSCTL1 (RSEL) registers.

There are some problems with the factory calibration values:

  • They can only be used at the calibration points availables for the selected MCU. The G2211 has only one calibration value for 1MHz. The G2553 is calibrated for 1, 8 and 16 MHz. If you want to operate at 4MHz in this MCU you need to do the calibration yourself.
  • Texas Instruments calibrates at 30ºC and 3V  supply. If you want to operate out of this conditions, a custom calibration could be better than the factory one.

Custom DCO calibration

The calibration procedure needs to check the DCO against a good reference. As the Launchpad board comes with a 32768 Hz quartz, we can use its as reference. You can se an article about this Xtal on another entry of this blog.

You can find on the Internet several references about the calibration procedure like this one or this another. There is also Texas Instruments library to do that.

Most solutions rewrite Section A of the Flash Information Area inside the MCU. That area is used by Texas Instruments to store some calibration data of their MCUs. This area is protected by a special security flag (LOCKA) to prevent an accidental erase of calibration data.

The rewrite of Information Area Section A is dangerous as we could erase not only the DCO calibration data but also the ADC calibration data. Moreover, this area includes a checksum that some applications could try to check. You could recalculate the checksum but you cannot recover the ADC data if it was not saved before erasing it.

Section A is only one of the four information areas included on the MSP430 MCUs. All four areas A, B, C and D have a 64 byte size. Section A is filled at the TI factory but areas B, C and D are empty and free for the user programs. I think that storing the user DCO calibration data could be done on Section B and leave Section A  with factory data.

I selected 9 frequency values for calibration: 500Hz, 1MHz, 2MHz, 4MHZ, 6MHz, 8 MHz, 10MHz, 12MHz and 16 MHz. In the next section I describe the program used for calibration.


DCO Calibration program

main.c

The DCO Calibration program main.c file contains most of the code. It compiles under MSP GCC and should fit in any MSP430 MCU with at least 2kB Flash. Once the program runs and the calibration data is stored in the flash, it can be erased. Any future program could use the stored calibration values.

/*
Clock Calibration Project (main.c)
Calibrates the DCO using the CLK32 XTal
This program provides serveral calibration frequencies for the DCO
Uses an external 32768 Hz used as a reference
The program calibrates the DCO clock for the following frequences:
500kHz,1MHz,2MHz,4MHz,6MHz,8MHz,10MHz,12MHZ,16MHz
But it's easy to change this values
The calibrated DCOCTL and BCSCTL1 values are stored in the
flash information memory Section B.
The file NewDCOCal.h provides the positions of each calibrated value
The complete program can be used in MSP430 value line MCUs with
2kB of flash memory or more if size optimization is used for compiling.
External pins used:
Xin / Xout : External 32768 Hz 12.5pF Quartz Cristal
P1.0 : Red Led to ground
P1.3 : Pushbutton to ground
P1.4 : Outputs MCLK
P1.5 : Outputs 32kHz clock divided by 128
P1.6 : Green Led to ground
Program operation:
When the program starts, it checks if there is data in the
calibration area in information area section B.
If there is data it enters loop frequency mode.
If there is no data, it calibrates the selected frequencies and
stores the calibration data in information area section B
then, it enters in loop frequency mode.
During calibration the P.14 pin outputs the current DCO frequency.
During calibration the green led blinks each time it changes the frequency.
If a frequency scan must be repeated, both red and green leds will blink.
In loop frequency mode, the system programs the DCO to the first
calibration data in section B. A push of the button changes to the
next frequency. After the last frequency it returns to the first one.
During loop frequency mode the P1.4 pin outputs DCO frequency.
In loop frequency mode the gren led blinks with a frequency
equal to DCO frequency divided by 20*2^16 (20 Timer A0 Overflows)
In case of error the program locks showing an error code using a
number of blinks on the red led:
1 32768 Hz oscilator fault
2 One Frequency cannot be obtained
4 No factory DCO cal for 1MHz to guarantee flash timing
5 A calibration has more tan the maximum allowed frequency error
Continuous red led: 32768 Hz oscilator fault
Calibration operation:
Watchdog is configured as timer with 64/fclk32 period
Timer A is configured with DCO input
The number of DCO cycles (diff) between Watchdog overflows is computed.
The DCO frequency is calculated:
DCO freq = 512 diff
The counter differences are averaged during NCAP cycles (50 by default) to
reduce the error.
Conditional compilation defines:
If the DEBUG define is activated, the program stores the
RSEL,DCO and MOD oscillator values in array for each one.
It also stores the frequency error of each calibration.
If the FLASH_OVERRIDE define is activated, the program will override
any value stored in the flash information area section B.
If the TEST_MODE define is activated, the program will do the calibration
and the final loop frequency mode, but all the information will be
stored only on RAM so, the flash information area will not be used at all.
*/
/********** CONDITIONAL COMPILATION DEFINES ************************/
// Flash Override
// If active, calibration data will override non blank flash on Section B
//#define FLASH_OVERRIDE
// Test Mode
// Do not touch or reads the flash
// Only works in RAM to test operations
//#define TEST_MODE
// Debug define
//#define DEBUG // Eliminate to reduce code and memory usage
/***************** OTHER OPERATION DEFINES *****************************/
// Max allowed error (in %)
#define MAX_ERROR 5
// Max test cycles for each frequency
#define MAX_CYCLES 10
// Number of captures to average
#define NCAP 50
/********************* INCLUDES ***************************************/
#include <msp430.h>
#include <msp430g2553.h>
#include <legacymsp430.h> // Needed for interrupts
#include "io430masks.h" // My SET/RESET macros
#include "NewDCOCal.h" // Positions to store new calibration data
/******************** OTHER DEFINES **********************************/
#define LED_RED BIT0 // Red LED at P1.0
#define LED_GREEN BIT6 // Green LED at P1.6
#define F_OUT BIT5 // Freq out for CLK32/128
#define SMCLK_PIN BIT4 // SMCLK output at P1.4
#define SWITCH BIT3 // Switch input
/******** Constants with data for the frequecies to scan *************/
// Number of frequencies to scan
#define NFREQ 9
// Scan frequencies in kHz
#ifdef DEBUG
const unsigned int FreqK[NFREQ] ={500,1000,2000,4000,6000 ,8000 ,10000,12000,16000};
#endif
// Goal counter values for each frequency (freq(Hz)/512)
const unsigned int GoalN[NFREQ] ={977,1953,3906,7813,11719,15625,19531,23438,31250};
// Calibration data position for each frequency (from NewDCOCal.h)
const unsigned int CalPos[NFREQ]={NCALDCO_500kHZ_
,NCALDCO_1MHZ_
,NCALDCO_2MHZ_
,NCALDCO_4MHZ_
,NCALDCO_6MHZ_
,NCALDCO_8MHZ_
,NCALDCO_10MHZ_
,NCALDCO_12MHZ_
,NCALDCO_16MHZ_};
/*************** VARIABLES **********************************/
// Calibration data for each frequency
// To be stored in information memory at the end of calibration
unsigned char CalDCO[NFREQ];
unsigned char CalBC1[NFREQ];
// Data found for each frequency (Only in DEBUG mode)
#ifdef DEBUG
unsigned char FoundRsel[NFREQ]; // RSEL value
unsigned char FoundDco[NFREQ]; // DCO value
unsigned char FoundMod[NFREQ]; // Modulation value
char FoundErr[NFREQ]; // Frequency error in %
#endif
// Last captured value on Timerer A2 module 0
unsigned int LastCapture=0;
// Last difference in captured values
unsigned int LastDifference;
// Averaged differences
unsigned int AveragedDifference;
// Number of captures
volatile int ncap=0;
// Current oscilator values during calibration
int rsel=0;
int dco=0;
int mod=0;
// Long value to average
unsigned long int Mean;
// Blink counter
int bcount=0;
/*************************** FUNCTIONS **********************/
// Simple delay function
void simpleDelay()
{
__delay_cycles(1000);
}
// Long delay function
void longDelay()
{
int i;
for(i=0;i<20;i++)
__delay_cycles(10000);
}
// Locks the system with an error code
// Error code must be 1 or greater
// The error is shown with a number of blinks
// This function never returns
void errorLock(int error)
{
int i;
// Calibrated DCO data for 1MHz
DCOCTL=CALDCO_1MHZ; // DCOCTL Cal. data for 1MHz
BCSCTL1=CALBC1_1MHZ; // BCSCTL1 Cal. data for 1MHz
while (1)
{
// As blinks as error code number
for(i=0;i<error;i++)
{
SET_FLAG(P1OUT,LED_RED);
longDelay();
RESET_FLAG(P1OUT,LED_RED);
longDelay();
}
// A final space to separate the blink series
for(i=0;i<8;i++)
longDelay();
}
}
// One fast blink on green led
void ledBlink(unsigned char bits)
{
SET_FLAG(P1OUT,bits);
longDelay();
RESET_FLAG(P1OUT,bits);
}
// Start of the Clk32 clock
// Uses default value of DIVA_0
void startClk32()
{
do
{
BCSCTL3=XCAP_3; // Set 12.5pF
simpleDelay();
}
while (BCSCTL3&LFXT1OF); // Loop while faulty
}
// Configure all the peripherals
void configureAll()
{
// Calibrated DCO data for 1MHz
DCOCTL=CALDCO_1MHZ; // DCOCTL Cal. data for 1MHz
BCSCTL1=CALBC1_1MHZ; // BCSCTL1 Cal. data for 1MHz
// Set LEDs and F_OUT clk32/128 as output
SET_FLAG(P1DIR,LED_RED+LED_GREEN+F_OUT+SMCLK_PIN);
// Turn on red led, clear the rest of P1
P1OUT=LED_RED;
// Start the 32k clock
startClk32();
// Turn OFF port 1 (Red led included)
P1OUT=0;
// Configure WDT as a timer
WDTCTL=WDT_ADLY_1_9; // t=64/fACLK=1.9ms
SET_FLAG(IE1,WDTIE); // Enable WDT interrupt
// Configure the Timer A
// TACTL
// TASSEL_2 Use SMCLK
// ID_0 Divide by 1
// MC_2 Mode Up Continuous
TACTL=TASSEL_2+ID_0+MC_2;
// TACCTL0
// CAP Capture mode
// CCIE Enable capture 0 interrupt
// CM_3 Capture on both edges
// CCIS_0 Capture on GND
TACCTL0=CAP+CCIE+CM_3+CCIS_2;
// Program P1.4 as SMCLK output
SET_FLAG(P1SEL,SMCLK_PIN);
}
// Programs the DCO with
// rsel: Range 0..15
// dco: Step 0..7
// mod: Modulation 0..31
// Then lets the Oscilator work for several NCAP captures
// to average the frequency measure.
void setDCO(int rsel,int dco,int mod)
{
// Set BSCTL1
BCSCTL1=XT2OFF+rsel;
// Set DCOCTL
DCOCTL=dco*DCO0+mod;
// Loops for several captures
dint();
Mean=0; // No captures yet
ncap=-5; // 5 cycles before start averaging
eint();
while (ncap<NCAP) {};
AveragedDifference=(unsigned int)(Mean/NCAP);
}
// Search for the indicated goal counter value
// Returns 0 if the frequency is obtained
// Returns 1 if cannot be obtained
int searchGoal(unsigned int CurrentNGoal)
{
unsigned int prev;
int m0diff;
// Search RSEL ranges with central DCO and
// no modulation
for(rsel=0;rsel<16;rsel++)
{
// Set central DCOx
setDCO(rsel,3,0);
if (AveragedDifference>CurrentNGoal) break;
prev=AveragedDifference;
}
if (rsel<16) // If we breaked...
{
if (rsel) // and there is a previous rsel...
// and error was less before...
if ((CurrentNGoal-prev)<(AveragedDifference-CurrentNGoal))
rsel--;
}
else
rsel=15; // If we don't breaked, limit rsel to 15
// Now we have RSEL value so we search for DCO value
// Search DCO value
for(dco=0;dco<8;dco++)
{
setDCO(rsel,dco,0);
if (AveragedDifference>CurrentNGoal) break;
}
// If we did not break cannot modulate
if (dco>7) return 1;
// If dco=0 we cannot modulate also
if (!dco) return 1;
// Backup value of last error before MOD modulation
m0diff=AveragedDifference-CurrentNGoal;
// Now we have DCO value we then search for MOD value
// Take base freq less than over goal
dco--;
// Search MOD value
for(mod=0;mod<32;mod++)
{
setDCO(rsel,dco,mod);
if (AveragedDifference>CurrentNGoal) break;
prev=AveragedDifference;
}
if (mod<32) // If we breaked
{
if (mod) // If mod not zero...
// If lower mod got less error...
if ((AveragedDifference-CurrentNGoal)>(CurrentNGoal-prev))
mod--;
}
else // If we didn't breaked...
{
mod=31; // Limit for mod
}
// Check special case
if (mod==31)
if (m0diff<(AveragedDifference-CurrentNGoal))
{
dco++;
mod=0;
}
// Set final DCO configuration
setDCO(rsel,dco,mod);
return 0;
}
// Test if the flash to program in section B is empty
// If the zone has any data, return 1
// If the zone is empty, return 0
int testFlashEmpty()
{
int i;
unsigned char *ptr;
// Check every frequency cal location
for(i=0;i<NFREQ;i++)
{
ptr=(unsigned char *) CalPos[i];
if ((*(ptr++))!=0xFF) return 1; // Check DCOCTL
if (*ptr!=0xFF) return 1; // Check BCSCTL1
}
return 0;
}
// Writes the calibration data found to flash
// information area in section B
void flashWrite()
{
int i;
unsigned char *pFlash;
// Flash Timing
// Test for 1MHz calibration data
// as we need known DCO frequency for flash timing.
if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)
errorLock(4);
dint(); // Disable interrupts in flash operation
// Set calibrated DCO data for 1MHz
DCOCTL=CALDCO_1MHZ; // DCOCTL Cal. data for 1MHz
BCSCTL1=CALBC1_1MHZ; // BCSCTL1 Cal. data for 1MHz
// Set Flash freq. as 333kHz
// It's supposed that MCLK is 1MHz
// Divider is 3
FCTL2 = FWKEY + FSSEL_1 + FN1;
// Unlock the flash
FCTL3=FWKEY;
// We only need to erase if override
#ifdef FLASH_OVERRIDE
// Erase individual segment only
FCTL1 = FWKEY + ERASE;
// Dummy Write
pFlash = (unsigned char *) CalPos[0];
(*pFlash)=0;
#endif
// Set write mode
FCTL1 = FWKEY + WRT;
// Write calibration data
for(i=0;i<NFREQ;i++)
{
pFlash = (unsigned char *) CalPos[i];
(*pFlash)=CalDCO[i];
pFlash++;
(*pFlash)=CalBC1[i];
}
// End flash operation
FCTL1 = FWKEY; // Clear WRT bit
FCTL3 = FWKEY + LOCK; // Set LOCK bit
eint(); // Enable interrupts
}
// Loop through all the frequencies
// If flash is true, it loops through flash data
// If not, it loops thorugh RAM data
// Never returns
void loopFrequencies(int flash)
{
int i;
unsigned char *pFlash;
// Turn On Green Led
SET_FLAG(P1OUT,LED_GREEN);
// DCO is set for SMCLK
// Enable TAR Timer Overflow Interrupt
// Used to blink the green led
SET_FLAG(TACTL,TAIE);
// Program SWITCH as input with pull-up
RESET_FLAG(P1DIR,SWITCH); // Input mode
SET_FLAG(P1OUT,SWITCH); // Output "1" for Pull-Up
SET_FLAG(P1REN,SWITCH); // Resistor Enable
i=0;
while (1) // Do forever
{
if (flash)
{
// Set the frequency from flash
pFlash = (unsigned char *) CalPos[i];
DCOCTL=*pFlash;
pFlash++;
BCSCTL1=*pFlash;
}
else
{
// Set the frequency form memory table
DCOCTL=CalDCO[i];
BCSCTL1=CalBC1[i];
}
// Wait to release switch if was pressed
while (!(P1IN&SWITCH)) {};
// Turn Off Red Led
RESET_FLAG(P1OUT,LED_RED);
// Loops for several captures to debounce
ncap=0;
while (ncap<200) {};
// Wait to press switch
while (P1IN&SWITCH) {};
// Turn On Red Led
SET_FLAG(P1OUT,LED_RED);
// Increase frequency
i++;
if (i>=NFREQ) i=0;
}
}
// Main function
int main()
{
int i,j,error;
// Disable the watchdog.
WDTCTL = WDTPW + WDTHOLD;
// Configure all the peripherals
configureAll();
// Enable interrupts
eint();
// Flash is tested to be empty if
// test mode and flash override are not selected
#ifndef TEST_MODE
#ifndef FLASH_OVERRIDE
// Test if the zone to program is empty
if (testFlashEmpty())
{
// Loop from flash if data found
loopFrequencies(1);
}
#endif
#endif
// Search for each frequency
for(i=0;i<NFREQ;i++)
{
// If clock fault, generate error 1
if (BCSCTL3&LFXT1OF) errorLock(1);
// Blinks green led
ledBlink(LED_GREEN);
// Search for each frequency
for(j=0;j<MAX_CYCLES;j++)
{
// Blinks red led if repeating
if (j) ledBlink(LED_GREEN+LED_RED);
// Error lock 2 if cannot adquire frequency
if (searchGoal(GoalN[i])) errorLock(2);
// Compute frequency error in %
error=(char)((100*((long int)AveragedDifference-(long int)GoalN[i]))/GoalN[i]);
if ((error<=MAX_ERROR)&&(error>=(-MAX_ERROR))) break;
}
// If we didn't breaked then we are out of tolerance error
if (j>=MAX_CYCLES) errorLock(5);
// Store calibration data
CalDCO[i]=DCOCTL;
CalBC1[i]=BCSCTL1;
// Store debug information if enabled
#ifdef DEBUG
FoundRsel[i]=rsel;
FoundDco[i]=dco;
FoundMod[i]=mod;
FoundErr[i]=error;
#endif
}
// Return to calibrated DCO data for 1MHz
DCOCTL=CALDCO_1MHZ; // DCOCTL Cal. data for 1MHz
BCSCTL1=CALBC1_1MHZ; // BCSCTL1 Cal. data for 1MHz
#ifdef TEST_MODE
// Loop from ram table
loopFrequencies(0);
#endif
// Write the flash
flashWrite();
// Loop from flash
loopFrequencies(1);
// We should never arrive to this point
// as loopFrequencies never returns
// Locks with green led
SET_FLAG(P1OUT,LED_GREEN);
while(1) {};
// This function never returns
return 0;
}
/********************** RSI FUNCTIONS *******************/
// Watchdog timer interrupt
// Called each 64/fCLK32 period (1,9ms)
// Generates a capture in module 0 of timer 0
// each time it is called.
interrupt(WDT_VECTOR) WDT_ISR(void)
{
// Don't need to clear any flag
// Toggle Fout
P1OUT^=F_OUT;
// Generate a capture by changing the capture
// input between Vdd and GND using CCIS0
TACCTL0^=CCIS0;
}
// Capture 0 interrupt
// A capture is generated each time the
// Watchdog timer overflows
// The RSI computes the diference between captures
interrupt(TIMER0_A0_VECTOR) CAPTURE0_ISR(void)
{
// Don't need to clear any flag
// Current distance between captures
LastDifference=TACCR0-LastCapture;
// Current last capture
LastCapture=TACCR0;
if (ncap>=0)
if (ncap<NCAP)
Mean+=LastDifference;
// We don't want to roll over
if (ncap<10000) ncap++;
}
// General Timera A0 ISR
interrupt(TIMER0_A1_VECTOR) Timer0_ISR(void)
{
if (TA0IV==TA0IV_TAIFG)
{
bcount++;
if (bcount>=10)
{
P1OUT^=LED_GREEN;
bcount=0;
};
}
}
view raw main.c hosted with ❤ by GitHub


Two source files are linked to main.c, io430masks.h gives some register mask values and NewDCOCal.h set the flash memory addressed of the calibration data generated by the program.

io430masks.h

/*********************************************
Masks for register bit fields
(c) VJS
Version 28-06-2011
Modificaciones:
28-06-2011 : Version inicial
17-04-2012 : Cambios en la definicion de algunas mascaras
**********************************************/
/*********************************************
Example for a 8 bit register REG with
1 bit field BIT
2 bit field F0,F1
REG|=BIT To set BIT
REG&=~BIT To clear BIT
For F0,F1 a mask is defined:
F01_MASK = (0xFF-3*F0)
REG&=F01_MASK To clear field
REG=(REG&F01_MASK)+F0 To set F0 and not F1
REG=(REG&F01_MASK)+F1 To set F1 and not F0
REG=(REG&F01_MASK)+F0+F1 To set F0 and F1
REF=(REG&F01_MASK)+(F0*value) To set value 0..3
***********************************************/
/*************** BASIC DEFINITIONS *****************
This definitions define the following macros:
SET_FLAG(REGISTER,FLAG) Set a 1 bit flag
RESET_FLAG(REGISTER,FLAG) Reset a 1 bit flag
RESET_FIELD(REGISTER,MASK) Clear a multibit field
SET_FIELD(REGISTER,MASK,VALUE)
Set a multibit field (value must be consistent with mask)
SET_FIELD_WOFFSET(REGISTER,MASK,VALUE,OFFSET)
Set a multibit field adding an offset
El nombre del campo referencia al ultimo bit del campo, no el primero
** Posibilidad de modificar las macros para usar BIS y BIC
***************************************************/
#define SET_FLAG(REGISTER,FLAG) REGISTER|=(FLAG)
#define RESET_FLAG(REGISTER,FLAG) REGISTER&=~(FLAG)
#define CLEAR_FIELD(REGISTER,MASK) REGISTER&=(MASK)
#define SET_FIELD(REGISTER,MASK,VALUE) REGISTER=(REGISTER&(MASK))+VALUE
#define SET_FIELD_WOFFSET(REGISTER,MASK,VALUE,OFFSET) REGISTER=(REGISTER&(MASK))+((VALUE)*(OFFSET))
#define GET_FIELD(REGISTER,MASK) (REGISTER&(MASK))
/**************** ADC 10 **********************/
#ifdef __MSP430_HAS_ADC10__
/* ADC10CTL0 @ 0x01B0 ADC10 Control 0 */
#define ADC10SHT_OFFS (0x800u)
#define ADC10SHT_MASK (0xFFFF-3*ADC10SHT_OFFS) /* 2 bit mask */
#define ADC10SREF_OFFS (0x2000u)
#define ADC10SREF_MASK (0xFFFF-7*ADC10SREF_OFFS) /* 3 bit mask */
/* ADC10CTL1 @ 0x01B2 ADC10 Control 1 */
#define ADC10CONSEQ_OFFS (2u)
#define ADC10CONSEQ_MASK (0xFFFF-3*ADC10CONSEQ_OFFS) /* 2 bit mask */
#define ADC10SSEL_OFFS (8u)
#define ADC10SSEL_MASL (0xFFFF-3*ADC10SSEL_OFFS) /* 2 bit mask */
#define ADC10DIV_OFFS (0x20u)
#define ADC10DIV_MASK (0xFFFF-7*ADC10DIV_OFFS) /* 3 bit mask */
#define SHS_OFFS (0x400u)
#define SHS_MASK (0xFFFF-3*SHS_OFFS) /* 2 bit mask */
#define INCH_OFFS (0x1000u)
#define INCH_MASK (0xFFFFu-0xF*INCH_OFFS) /* 4 bit mask */
#endif
/********* BASIC CLOCK MODULE ****************/
#ifdef __MSP430_HAS_BC2__
/* DCOCTL @ 0x0056 DCO Clock Frequency Control */
#define MOD_OFFS (MOD0)
#define MOD_MASK (0xFF-0x1F*MOD0) /* 5 bit mask */
#define DCO_OFFS (DCO0)
#define DCO_MASK (0xFF-7*DCO0) /* 3 bit mask */
/* BCSCTL1 @ 0x0057 Basic Clock System Control 1 */
#define RSEL_OFFS (RSEL0)
#define RSEL_MASK (0xFF-0xF*RSEL0) /* 4 bit mask */
#define DIVA_OFFS (DIVA0)
#define DIVA_MASK (0xFF-0x3*DIVA0) /* 2 bit mask */
/* BCSCTL2 @ 0x0058 Basic Clock System Control 2 */
#define DIVS_OFFS (DIVS0)
#define DIVS_MASK (0xFF-0x3*DIVS0) /* 2 bit mask */
#define DIVM_OFFS (DIVM0)
#define DIVM_MASK (0xFF-0x3*DIVM0) /* 2 bit mask */
#define SELM_OFFS (SELM0)
#define SELM_MASK (0xFF-0x3*SELM0) /* 2 bit mask */
/* BCSCTL3 @ 0x0053 Basic Clock System Control 3 */
#define XCAP_OFFS (XCAP0)
#define XCAP_MASK (~(XCAP0|XCAP1)) /* 2 bit mask */
#define LFXT1S_OFFS (LFXT1S0)
#define LFXT1S_MASK (~(LFXT1S0|LFXT1S1)) /* 2 bit mask */
#define XT2S_OFFS (XT2S0)
#define XT2S_MASK (0xFF-0x3*XT2S0) /* 2 bit mask */
#endif
/******** FLASH MEMORY *************************/
#ifdef __MSP430_HAS_FLASH2__
/* FCTL2 @ 0x012A FLASH Control 2 */
#define FN_OFFS (FN0)
#define FN_MASK (0xFF-0x3F*FN0) /* 6 bit mask */
#define FSSEL_OFFS (FSSEL0)
#define FSSEL_MASK (0xFF-0x3*FSSEL0) /* 2 bit mask */
#endif
/******* DIGITAL I/O PORTS 1/2 *****************/
/* No definitions are needed here */
/********** TIMER A2 ***************************/
#ifdef __MSP430_HAS_TA2__
/* TAIV @ 0x012E Timer A Interrupt Vector Word (Read Only) */
#define IRQ_OFFS (0x0x2)
#define IRQ_MASK (0xFFFF-7*IRQ_OFFS) /* 3 bit mask */
/* Mask not normally needed as it is read only !!! */
/* TACTL @ 0x0160 Timer A Control */
#define TAMC_OFFS (0x10u)
#define TAMC_MASK (0xFFFF-3*TAMC_OFFS) /* 2 bit mask */
#define TAID_OFFS (0x40u)
#define TAID_MASK (0xFFFF-3*TAID_OFFS) /* 2 bit mask */
#define TASSEL_OFFS (0x100u)
#define TASSEL_MASK (0xFFFF-3*TASSEL_OFFS) /* 2 bit mask */
/* TACCTL0 @ 0x0162 Timer A Capture/Compare Control 0 */
#define OUTMOD_OFFS (0x20u)
#define OUTMOD_MASK (0xFFFF-0x7*OUTMOD_OFFS) /* 3 bit mask */
#define CCIS_OFFS (0x1000u)
#define CCIS_MASK (0xFFFF-0x3*CCIS_OFFS) /* 2 bit mask */
#define CM_OFFS (0x4000u)
#define CM_MASK (0xFFFF-0x3*CM_OFFS) /* 2 bit mask */
/* TACCTL1 @ 0x0164 Timer A Capture/Compare Control 1 */
/* Las máscaras son iguales a las de TACCTL0 */
/* Aditional Definitions */
#define TAIV_NONE 0x00
#define TAIV_TACCR1 0x02
#define TAIV_TACCR2 0x04
#define TAIV_TAIFG 0x0A
#endif
/************** USI ****************************/
#ifdef __MSP430_HAS_USI__
/* USICKCTL @ 0x007A USI Clock Control Register */
#define USISSEL_OFFS (USISSEL0)
#define USISSEL_MASK (0xFF-7*USISSEL0) /* 3 bit mask */
#define USIDIV_OFFS (USIDIV0)
#define USIDIV_MASK (0xFF-7*USIDIV0) /* 3 bit mask */
/* USICNT @ 0x007B USI Bit Counter Register */
#define USICNT_OFFS (USICNT0)
#define USICNT_MASK (0xFF-0x1F*USICNT0) /* 5 bit mask */
#endif
/************** WATCHDOG **********************/
/* Mask cannot be used as it is protected by a password */
/************** COMPARATOR A *****************/
#ifdef __MSP430_HAS_COMPA__
/* CACTL1 @ 0x0059 Comparator A Control 1 */
#define CAREF_OFFS (CAREF0)
#define CAREF_MASK (0xFF-0x3*CAREF0) /* 2 bit mask */
/* CACTL2 @ 0x005A Comparator A Control 2 */
#define P2CA_OFFS (P2CA0)
#define P2CA_MASK (0xFF-0x3*P2CA0) /* 2 bit mask */
/* Definitions for missing signals in .h */
#define P2CA2 BIT4
#define P2CA3 BIT5
#define P2CA4 BIT6
#define CASHORT BIT7
#define SELP_MASK (0xFF-P2CA0-P2CA4)
#define SELP_NONE (0x00)
#define SELP_CA0 (P2CA0)
#define SELP_CA1 (P2CA4)
#define SELP_CA2 (P2CA0+P2CA4)
#define SELN_MASK (0xFF-7*P2CA1)
#define SELN_NONE (0x00)
#define SELN_CA1 (1*P2Ca1)
#define SELN_CA2 (2*P2Ca1)
#define SELN_CA3 (3*P2Ca1)
#define SELN_CA4 (4*P2Ca1)
#define SELN_CA5 (5*P2Ca1)
#define SELN_CA6 (6*P2Ca1)
#define SELN_CA7 (7*P2Ca1)
#endif
view raw io430masks.h hosted with ❤ by GitHub


NewDCOCal.h

/*
NewDCOCal.h
New calibration data for the DCO
*/
#include <iomacros.h>
// Test ton include the file only one time
#ifndef _NEW_DCO_CAL_
#define _NEW_DCO_CAL_
#define NCALDCO_500kHZ_ 0x10AE /* DCOCTL Calibration Data for 500kHz */
const_sfrb(NCALDCO_500kHZ, NCALDCO_500kHZ_);
#define NCALBC1_500kHZ_ 0x10AF /* BCSCTL1 Calibration Data for 500kHz */
const_sfrb(NCALBC1_500kHZ, NCALBC1_500kHZ_);
#define NCALDCO_16MHZ_ 0x10B0 /* DCOCTL Calibration Data for 16MHz */
const_sfrb(NCALDCO_16MHZ, NCALDCO_16MHZ_);
#define NCALBC1_16MHZ_ 0x10B1 /* BCSCTL1 Calibration Data for 16MHz */
const_sfrb(NCALBC1_16MHZ, NCALBC1_16MHZ_);
#define NCALDCO_12MHZ_ 0x10B2 /* DCOCTL Calibration Data for 12MHz */
const_sfrb(NCALDCO_12MHZ, NCALDCO_12MHZ_);
#define NCALBC1_12MHZ_ 0x10B3 /* BCSCTL1 Calibration Data for 12MHz */
const_sfrb(NCALBC1_12MHZ, NCALBC1_12MHZ_);
#define NCALDCO_10MHZ_ 0x10B4 /* DCOCTL Calibration Data for 10MHz */
const_sfrb(NCALDCO_10MHZ, NCALDCO_10MHZ_);
#define NCALBC1_10MHZ_ 0x10B5 /* BCSCTL1 Calibration Data for 10MHz */
const_sfrb(NCALBC1_10MHZ, NCALBC1_10MHZ_);
#define NCALDCO_8MHZ_ 0x10B6 /* DCOCTL Calibration Data for 8MHz */
const_sfrb(NCALDCO_8MHZ, NCALDCO_8MHZ_);
#define NCALBC1_8MHZ_ 0x10B7 /* BCSCTL1 Calibration Data for 8MHz */
const_sfrb(NCALBC1_8MHZ, NCALBC1_8MHZ_);
#define NCALDCO_6MHZ_ 0x10B8 /* DCOCTL Calibration Data for 6MHz */
const_sfrb(NCALDCO_6MHZ, NCALDCO_6MHZ_);
#define NCALBC1_6MHZ_ 0x10B9 /* BCSCTL1 Calibration Data for 6MHz */
const_sfrb(NCALBC1_6MHZ, NCALBC1_6MHZ_);
#define NCALDCO_4MHZ_ 0x10BA /* DCOCTL Calibration Data for 4MHz */
const_sfrb(NCALDCO_4MHZ, NCALDCO_4MHZ_);
#define NCALBC1_4MHZ_ 0x10BB /* BCSCTL1 Calibration Data for 4MHz */
const_sfrb(NCALBC1_4MHZ, NCALBC1_4MHZ_);
#define NCALDCO_2MHZ_ 0x10BC /* DCOCTL Calibration Data for 2MHz */
const_sfrb(NCALDCO_2MHZ, NCALDCO_2MHZ_);
#define NCALBC1_2MHZ_ 0x10BD /* BCSCTL1 Calibration Data for 2MHz */
const_sfrb(NCALBC1_2MHZ, NCALBC1_2MHZ_);
#define NCALDCO_1MHZ_ 0x10BE /* DCOCTL Calibration Data for 1MHz */
const_sfrb(NCALDCO_1MHZ, NCALDCO_1MHZ_);
#define NCALBC1_1MHZ_ 0x10BF /* BCSCTL1 Calibration Data for 1MHz */
const_sfrb(NCALBC1_1MHZ, NCALBC1_1MHZ_);
#endif // NewDCOCal
view raw NewDCOCal.h hosted with ❤ by GitHub


The program is designed to be run on the Launchpad G2 board and  needs the 32768 Hz Xtal to be populated. It also uses the green LED at P1.0, the red LED at P1.6 and the user button at P1.3.

Two MCU pins are also used to check the calibration frequencies:

       P1.4: Square wave at DCO frequency
       P1.5: 256 Hz square wave generated from the 32768 Hz Xtal

When the program starts it checks if there is data in Information Area Section B. If there is any information it goes to the generation frequency mode that I will explain later. If it is empty, the calibration is carried out and the obtained data is stored in Section B jumping next to generating frequency mode.
During calibration, P1.4 shows the generated frequency. Every time a new frequency is selected for calibration the green LED makes a blink. If the calibration for one frequency must be repeated because it is outside the desired tolerance it is indicated with a blink of both green and red LEDs.
  
In frequency generation mode, entered after calibration or upon start if Section B is not empty, the system programs the first calibration frequency (500Hz). Each time the user button at P1.3 is pushed the next frequency is generated. After the last one (16MHz) the system restarts at the first one.
In this mode P1.4 shows the generated frequency and the green LED blinks at a frequency associated to 20 overflows of Timer A0 (20 times the count to 65536 from the DCO frequency).

In case of error the program locks itself with an error code indicated with a number of red LED blinks:

     (1) 32768 Hz clock fails
     (2) Cannot calibrate one frequency
     (4) There is no factory calibration data for 1MHz (It is needed to program the flash)
     (5) Cannot calibrate inside the selected tolerance (5% by default)

If the 32768 Hz clock cannot start at all, the system blocks with the red LED fixed in ON state.


Program operation

In order to do the calibration the Watchdog timer is programmed in timmer mode with about 1,9ms timeout (64 cycles at 32768 Hz) using the Xtal oscillator. Timer A0 is configured in continuous mode using the DCO as input. A capture is generated in A0 each time the Watchdog timer timeouts. We can calculate the DCO frequency from the number of DCO clock cycles between two captures:

DCO Frequency = 512 * Ncycles

To increase the precission the calculation is made averaging 50 captures.

The program starts choosing the RSEL value, then the DCO value just below the target frequency is selected. Finally the frequency is fine tuned using the MOD value.


Conditional compilation

The program uses three defines to configure its compilation.

If the DEBUG define is activated, the program stores the RSEL, DCO and MOD values next to the calibration error at each frequency. That data can be extacted using the debugger.

If the FLASH_OVERRIDE flag is active, the program will rewrite Information Area Section B even if it is not empty upon startup.

If the TEST_MODE is active, all the calibration data will be stored in RAM instead of flash. That way the program operation can be checked without touching the flash memory information areas.


Calibration video

The following video shows the system working in a v1.5 Launchpad board that has the default MSP430G2553 MCU installed.  Next to the board you can see the frequency generated both in an oscilloscope and a multimeter in frequency mode.





Source code is now ebedded (Update at 30/11/2014)

Thanks to Gist the source code is now embedded inside the blog entry.

Source code now in Github (Update at 11/02/2018) 


You can find it in this link:

https://github.com/R6500/MSP430-DCO-Calibration



No comments:

Post a Comment