Jump to content
Sign in to follow this  
oPossum

Comprehensive DCO calibration

Recommended Posts

The G series MSP430 have 1 or 4 factory calibrations for the DCO stored in info segment A. This firmware will store 96 DCO calibrations in info segments B, C, and D. After these calibrations have been stored it is possible to set the DCO to any frequency from 125 kHz to 16 MHz (128:1 range) with a simple function call...

 

set_dco.c

#include 

static const unsigned * const InfoSegA = (unsigned *)0x10C0;
static const unsigned * const InfoSegD = (unsigned *)0x1000;

int set_dco(const unsigned f)                       // Set DCO to specified frequency in kHz
{                                                   //
   unsigned i;                                     //
   unsigned rsel = 0;                              // Init rsel/dco
   unsigned dco = 0x20;                            //
   const unsigned *cal = InfoSegD;                 // Begin at info segment D
   do {                                            //
       if(f >= cal[0] && f < cal[1]) {             // Frequency in range?
           i = cal[1] - cal[0];                    // Interpolate for MODx
           dco += ((((f - cal[0]) * 0x20) + (i >> 1)) / i);
           DCOCTL = dco;                           // Set the DCO/MOD
           BCSCTL1 = rsel;                         //  and RSEL
           return 0;                               // Return success
       }                                           //
       if((dco += 0x20) == 0xC0)                   // Next DCO/RSEL
           dco = 0x20, ++rsel, ++cal;              //
   } while(++cal < InfoSegA);                      // While in info seg D, C, or B
   return -1;                                      // Return failure
}

 

Make sure that CCS is configured to write only to main memory so the calibration constants are not erased. This is not the default configuration.

post-2341-135135542657_thumb.png

 

The following code writes the DCO calibrations to the info segments. This only has to be done once for a specific chip. A 32 kHz crystal must be installed while this code is running, but is no longer needed once the calibration has been written.

 

Serial output while running calibration...

Info Segments - before calibration
D
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
C
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
B
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,65535,65535,65535,
A
35969,5886,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,4112,32786,0,
32209,742,0,32332,437,0,2302,65535,
65535,65535,65535,2049,36757,36504,36234,34506,

Calibrating...
99,106,114,123,134,146,122,131,
141,152,165,181,170,182,196,212,
229,251,241,258,278,299,323,354,
338,361,388,418,450,493,484,517,
555,596,642,703,673,718,770,827,
891,973,949,1012,1084,1163,1252,1368,
1348,1437,1538,1649,1776,1937,1924,2048,
2189,2346,2526,2754,2738,2911,3109,3329,
3586,3906,3520,3746,4007,4299,4635,5057,
4731,5032,5374,5761,6213,6770,6427,6824,
7278,7786,8385,9123,9350,9928,10587,11331,
12209,13283,12672,13414,14248,15200,16325,17715,

Info Segments - after calibration
D
99,106,114,123,134,146,122,131,
141,152,165,181,170,182,196,212,
229,251,241,258,278,299,323,354,
338,361,388,418,450,493,484,517,
C
555,596,642,703,673,718,770,827,
891,973,949,1012,1084,1163,1252,1368,
1348,1437,1538,1649,1776,1937,1924,2048,
2189,2346,2526,2754,2738,2911,3109,3329,
B
3586,3906,3520,3746,4007,4299,4635,5057,
4731,5032,5374,5761,6213,6770,6427,6824,
7278,7786,8385,9123,9350,9928,10587,11331,
12209,13283,12672,13414,14248,15200,16325,17715,
A
35969,5886,65535,65535,65535,65535,65535,65535,
65535,65535,65535,65535,65535,4112,32786,0,
32209,742,0,32332,437,0,2302,65535,
65535,65535,65535,2049,36757,36504,36234,34506,

Done

 

Calibration code...

 

main.c

#include 

void putc(unsigned);                // serial_tx.asm
void puts(char *);                  // serial_tx.asm
void utoa(unsigned, char *);        // utoa.asm

static char s[8];                   // buffer for printing unsigned int

                                   // Address of info segments
static unsigned * const InfoSegA = (unsigned *)0x10C0;
static unsigned * const InfoSegB = (unsigned *)0x1080;
static unsigned * const InfoSegC = (unsigned *)0x1040;
static unsigned * const InfoSegD = (unsigned *)0x1000;

/*
   Freq = Count / Time
   Freq = Count / (100 / 32768)  <-- 100 cycles of 32.768 kHz clock
   Freq (kHz) = Count / ((100 / 32768) * 1000)
   Freq (kHz) = Count / 3.0517578125
   Freq (kHz) = Count * (1 / 3.0517578125)
   Freq (kHz) = Count * 0.32768
   Freq (kHz) ~= (Count * 21475) / 65536
   Freq (kHz) ~= (Count * 21475) >> 16
   Freq (kHz) ~= ((Count * 21475) + 32768) >> 16  <-- Better rounding
*/

void cal_block(unsigned *d, unsigned rsel, unsigned dco)
{
   unsigned n = 32;                            // Do 32 cals beginning at rsel/dco with
                                               //  dco range of 0x20 to 0xA0 using 0x20 step
                                               //  - no modulation - 6 values per rsel
   DCOCTL = 0;                                 //
   DCOCTL = dco & 0xE0;                        // Set initial DCOx, MODx == 0
   BCSCTL1 = rsel;                             // Set initial RSELx
   do {                                        //
       __delay_cycles(3277);                   // Wait for DCO to settle for 100 ms
       TACTL |= TACLR;                         // Clear TimerA
       TACTL |= MC_2;                          // Start counting - TimerA continuous mode
       __delay_cycles(100 - 5);                // 100 cycles (5 overhead from previous and next instructions)
       TACTL &= ~MC_2;                         // Stop counting - TimerA stop mode
                                               //
       *d++ = ((21475L * TAR) + 32768) >> 16;  // Scale to kHz
                                               //
       if((DCOCTL += 0x20) == 0xE0) {          // Increment DCOx, check if limit reached
           DCOCTL = 0x20;                      // Reset DCOx
           ++BCSCTL1;                          // Next RSELx
       }                                       //
   } while(--n);                               //
}

void cal_write(const unsigned *c, unsigned *f)
{                                               // Write one full info segment - 64 bytes / 32 ints
   unsigned n;                                 //
   DCOCTL = 0;                                 // Run DCO at 1 MHz
   BCSCTL1 = CALBC1_1MHZ;                      //
   DCOCTL  = CALDCO_1MHZ;                      //
   FCTL2 = FWKEY | FSSEL_2 | 3 - 1;            // SMCLK / 3 for flash timing generator
   FCTL3 = FWKEY;                              // Clear lock bit
   FCTL1 = FWKEY | ERASE;                      // Set erase bit
   *f = 0;                                     // Dummy write to erase segment
   FCTL1 = FWKEY | WRT;                        // Set wrt bit for write operation
   n = 32; do *f++ = *c++; while(--n);         // Write flash
   FCTL1 = FWKEY;                              // Clear wrt bit
   FCTL3 = FWKEY | LOCK;                       // Set lock bit
}

void show_block(const unsigned *c)
{                                               // Show 32 unsigned ints
   unsigned a, b;                              //
   a = 4;                                      // 4 lines
   do {                                        //
       b = 8;                                  // 8 columns
       do {                                    //
           utoa(*c++, s);                      // Unsigned to ASCII
           puts(s);                            // Print it
           putc(',');                          //
       } while(--;                           // Next column
       puts("\r\n");                           // Next line
   } while(--a);                               //
}

void show_all_info_segs(void)
{
   puts("D\r\n");                              //
   show_block(InfoSegD);                       // D
   puts("C\r\n");                              //
   show_block(InfoSegC);                       // C
   puts("B\r\n");                              //
   show_block(InfoSegB);                       // B
   puts("A\r\n");                              //
   show_block(InfoSegA);                       // A
   puts("\r\n");                               //
}

void main(void)                                 //
{                                               //
   unsigned n;                                 //
   unsigned cal[32];
                                               //
   WDTCTL = WDTPW | WDTHOLD;                   // Disable watchdog reset
                                               //
   TACTL = TASSEL_2;                           // SMCLK (DCO)
                                               //
   P1DIR = BIT1;                               //
   P1OUT = BIT1;                               //
                                               //
   BCSCTL3 = XCAP_2;                           // 10 pF
   n = 0;                                      //
   do {                                        // - Wait for MSP430 to detect clock is stable
       IFG1 &= ~OFIFG;                         // Clear OFIFG
       while(--n);                             // Wait a while
   } while(IFG1 & OFIFG);                      // Loop until OFIFG remains cleared
   BCSCTL2 = SELM1 | SELM0;                    // - Use LFXT1CLK (32 kHz xtal) as clock source
                                               //
                                               // Show all the info segments before calibration
   puts("Info Segments - before calibration\r\n");
   show_all_info_segs();                       //
                                               //
   puts("Calibrating...\r\n");                 // Do calibration and write to flash info segments D to B
                                               //
   cal_block(cal, 0, 0x20);                    // Calibrate
   cal_write(cal, InfoSegD);                   // Write
   show_block(cal);                            // Show on terminal
                                               //
   cal_block(cal, 5, 0x60);                    //
   cal_write(cal, InfoSegC);                   //
   show_block(cal);                            //
                                               //
   cal_block(cal, 10, 0xA0);                   //
   cal_write(cal, InfoSegB);                   //
   show_block(cal);                            //
                                               //
   puts("\r\n");                               //
                                               // Show all the info segments after calibration has been written
   puts("Info Segments - after calibration\r\n");
   show_all_info_segs();                       //
                                               //
   puts("Done\r\n");                           //
                                               //
   for(;;                                    //          
}

 

serial_tx.asm

          .cdecls C, LIST, "msp430g2211.h"

           .text
           .def    putc, puts

           ; 32768 Hz / 2400 bps = 13.6533 cycles per bit ~= 41 cycles per 3 bits (14 / 13 / 14)


                                       ; Char to tx in R12
putc:       or      #0x0100, R12        ; Stop bit
           mov     #0x02, R15          ; Serial out bitmask
           jmp     bit_start           ;
                                       ;
bit0:       jmp     $ + 2               ;
           jmp     $ + 2               ;
           nop                         ;                                       
           rra     R12                 ; Bit 0/3/6
           jc      bit0h               ;
           bic.b   R15, &P1OUT         ; 14/55/96
           jmp     bit1                ;
bit0h:      bis.b   R15, &P1OUT         ; 14/55/96
           jmp     bit1                ;
                                       ;
bit1:       jmp     $ + 2               ;
           jmp     $ + 2               ;
           rra     R12                 ; Bit 1/4/7
           jc      bit1h               ;
           bic.b   R15, &P1OUT         ; 27/68/109
           jmp     bit2                ;
bit1h:      bis.b   R15, &P1OUT         ; 27/68/109
           jmp     bit2                ;
                                       ;
bit2:       jmp     $ + 2               ;
           jmp     $ + 2               ;
           nop                         ;                                       
           rra     R12                 ; Bit Start/2/5/Stop
           jc      bit2h               ;
bit_start:  bic.b   R15, &P1OUT         ; 0/41/82/-
           jmp     bit0                ;
bit2h:      bis.b   R15, &P1OUT         ; -/41/82/123
           jne     bit0                ;
ret_ins:    ret

puts:                                   ; Tx string using putc
           mov     R12, R14            ; String pointer in R12, copy to R14
putsloop:                               ;
           mov.b   @R14+, R12          ; Get a byte, inc pointer
           tst.b   R12                 ; Test if end of string
           jz      ret_ins             ; Yes, exit...
           call    #putc               ; Call putc
           jmp     putsloop            ;
                                       ;
           .end                        ;

 

utoa.asm

          .cdecls C, LIST, "msp430g2211.h"

           .text
           .def    utoa

utoa:                                   ; --- Unsigned to ASCII ---
                                       ; - Range 0 to 65535
                                       ; - Leading zeros supressed
       push    R10                     ;
       clr     R14                     ; Clear packed BCD
       .loop 13                        ; Do 13 bits
       rla     R12                     ; Get bit 15 to 3 of binary
       dadd    R14, R14                ; Multiply BCD by 2 and add binary bit
       .endloop                        ;
       clr     R15                     ; Clear digit 1 of packed BCD
       .loop 3                         ; Do 3 bits
       rla     R12                     ; Get bit 2 to 0 of binary
       dadd    R14, R14                ; Multiply BCD by 2 and add binary bit
       dadd    R15, R15                ;
       .endloop                        ;
       swpb    R14                     ; Swap digit order
       mov     R14, R12                ; Copy packed BCD digits
       and     #0x0F0F, R12            ; Mask digits 5 & 3
       rra     R14                     ; Shift digits 4 & 2 to lower nibble
       rra     R14                     ;
       rra     R14                     ;
       rra     R14                     ;
       and     #0x0F0F, R14            ; Mask digits 4 & 2
       mov     #('0' << 8) | '0', R10  ; Make ASCII
       add     R10, R12                ;
       add     R10, R14                ;
       add     R10, R15                ;
       cmp.b   R10, R15                ; Is first digit a 0?
       jne     dig5                    ; No...
       cmp.b   R10, R14                ; Is second digit a 0?
       jne     dig4                    ; No, only the first...
       cmp.b   R10, R12                ; Is third digit a 0?
       jne     dig3                    ; No, only the first two...
       cmp     R10, R14                ; Is fourth digit a 0? (second is zero)
       jne     dig2                    ; No, only the first three...
dig1:                                   ; First four digits are all 0
       swpb    R12                     ; Fifth digit to string
       mov.b   R12, 0(R13)             ;
       inc     R13                     ;
       clr.b   0(R13)                  ; NULL terminate string
       pop     R10                     ;
       ret                             ; Return
                                       ;
dig5:                                   ;
       mov.b   R15, 0(R13)             ; First digit to string
       inc     R13                     ;
dig4:                                   ;
       mov.b   R14, 0(R13)             ; Second digit to string
       inc     R13                     ;
dig3:                                   ;
       mov.b   R12, 0(R13)             ; Third digit to string
       inc     R13                     ;
dig2:                                   ;
       swpb    R14                     ; Fourth digit to string
       mov.b   R14, 0(R13)             ;
       inc     R13                     ;
       jmp     dig1                    ;

Share this post


Link to post
Share on other sites

This is very nice.

Now we need a header file with all CALBC1_xxHZ and CALDCO_xxHZ values.

Also, can you make a version that has a limited set of calibrations and fits into segment A only? That would allow segments B, C, and D to be used for something else.

Share this post


Link to post
Share on other sites

No need for a header file.

 

If you want 10 MHz just call...

 

set_dco(10000);

 

The calibration table isn't like the factory cal - for specific frequencies - it is the frequency of a specific RSEL/DCO setting. So the table in info memory is searchable for a pair of RSEL/DCO settings that surround the desired frequency, and then interpolation is used to get the MODx setting for the closest match.

 

This is what is stored - sans the first and last column.

Y axis (table data) is frequency, X axis (columns) is DCOx and 16 plots (rows) are RSELx

post-2341-13513554267_thumb.png

 

Since this works differently than the factory calibration constants, it isn't really adaptable to extending info segment A. The other threads on adding the 'missing' cal would be a better staring point.

Share this post


Link to post
Share on other sites

Here is a pretty picture showing how the cal data is stored in the info segments. Each info segment has 32 frequencies. The DCO and RSEL values are inferred from the address, they are not stored in the info segments. The first (0) and last (224) DCO values are not used. There is not room for them and there is enough overlap of the RSEL ranges to allow contiguous coverage without them.

 

To set a DCO frequency of 10 MHz (10000 kHz), a search of the table will find that 10000 is between 9932 and 10595. So RSEL will be 14 and DCO will be 64.

 

Interpolation is used to determine the MOD value...

 

10595 - 9932 = 663 (span of table entries)

10000 - 9932 = 68 (delta of target - low entry)

((68 * 32) + 331) / 663 = 3 (interpolate for 32 MODx values)

64 + 3 = 67 (final DCO/MOD (DCOCTL) value = 67)

post-2341-135135542682_thumb.png

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×