oPossum 1,083 Posted March 17, 2012 Share Posted March 17, 2012 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. 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 ; zeke, lvagasi, RobG and 4 others 7 Quote Link to post Share on other sites
RobG 1,892 Posted March 17, 2012 Share Posted March 17, 2012 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. Quote Link to post Share on other sites
oPossum 1,083 Posted March 17, 2012 Author Share Posted March 17, 2012 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 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. RobG 1 Quote Link to post Share on other sites
oPossum 1,083 Posted March 17, 2012 Author Share Posted March 17, 2012 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) Quote Link to post Share on other sites
zeke 693 Posted March 17, 2012 Share Posted March 17, 2012 This is kind of like making the swiss army knife out of the DCO. Very useful! Good Job! larryfraz 1 Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.