contiki/cpu/msp430/f2xxx/msp430.c
Atis Elsts 68b65e6c47 Fix CPU clock calibration in msp430f2xxx based platforms (e.g. Zolertia Z1).
The following problems were present in the existing DCO calibration algorithm:

Problem #1. In function msp430_quick_synch_dco(), the "for(i=0; i < 1000; i++) { .. }" loop is optimized away by the compiler, as i is not volatile. Making i volatile would improve the results, but would not be sufficient: see the next point.

Problem #2. According to MSP430F2617 Device Erratasheet, bug BCL12 precludes a naive implementations of "fast" calibration altogether. The bug is present on all MCU revisions up to date.

The description of the bug:
"After switching RSELx bits (located in register BCSCTL1) from a value of >13 to a value of <12 OR from a value of <12 to a value of >13, the resulting clock delivered by the DCO can stop before the new clock frequency is applied. This dead time is approximately 20 us. In some instances, the DCO may completely stop, requiring a power cycle.

Furthermore, if all of the RSELx bits in the BSCTL1 register are set, modifying the DCOCTL register to change the DCOx or the MODx bits could also result in DCO dead time or DCO hang up."

In Contiki code for msp430f2xxx @ 8MHz, the RSEL search currently typically goes from 15 down to 11, thus violating the rules.

Step-by-step RSEL change is proposed as the best possible workaround:
"[..] more reliable method can be implemented by changing the RSEL bits step by step in order to guarantee safe function without any dead time of the DCO."

Problem #3. The old Contiki code started from the highest possible calibration values: RSEL=15, DCOx=7. According to MSP430F2617
datasheet, this means that the DCO frequency is set to 26 MHz. For one, Vcc under 3V is not supported for this frequency, so this means that battery-powered nodes have a big problem. The minimal operating voltages are:
- 1.8V for RSEL <= 13
- 2.2V for RSEL = 14
- 3.0V for RSEL = 15
So the correct way is to always start calibration from RSEL <= 13, unless explicityly pre-calibred values are present.

Problem #4. Timer B should be turned off after the calibration, following the "Principles for Low-Power Applications" in MSP430 user's Guide.

The patch fixes these issues by performing step-by-step calibration and turning off Timer B afterwards. As opposed to MSP430F1xxx calibration, this algorithm does not change the ACLK divider beforehand; attempts to make calibration more precise would lead to looping in some cases, as the calibration step granularity at larger frequencies is quite big.

Additionally, the patch improves DCOSYNCH_CONF_ENABLED behavior, allowing the resynchronization to correct for more than one step.
2014-03-25 11:47:13 +01:00

271 lines
7.1 KiB
C

/*
* Copyright (c) 2005, Swedish Institute of Computer Science
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Contiki operating system.
*/
#include "contiki.h"
#include "dev/watchdog.h"
#if defined(__MSP430__) && defined(__GNUC__)
#define asmv(arg) __asm__ __volatile__(arg)
#endif
/*---------------------------------------------------------------------------*/
#if defined(__MSP430__) && defined(__GNUC__) && MSP430_MEMCPY_WORKAROUND
void *
w_memcpy(void *out, const void *in, size_t n)
{
uint8_t *src, *dest;
src = (uint8_t *) in;
dest = (uint8_t *) out;
while(n-- > 0) {
*dest++ = *src++;
}
return out;
}
#endif /* __GNUC__ && __MSP430__ && MSP430_MEMCPY_WORKAROUND */
/*---------------------------------------------------------------------------*/
#if defined(__MSP430__) && defined(__GNUC__) && MSP430_MEMCPY_WORKAROUND
void *
w_memset(void *out, int value, size_t n)
{
uint8_t *dest;
dest = (uint8_t *) out;
while(n-- > 0) {
*dest++ = value & 0xff;
}
return out;
}
#endif /* __GNUC__ && __MSP430__ && MSP430_MEMCPY_WORKAROUND */
/*---------------------------------------------------------------------------*/
void
msp430_init_dco(void)
{
if(CALBC1_8MHZ != 0xFF) {
DCOCTL = 0x00;
BCSCTL1 = CALBC1_8MHZ; /*Set DCO to 8MHz */
DCOCTL = CALDCO_8MHZ;
} else { /*start using reasonable values at 8 Mhz */
DCOCTL = 0x00;
BCSCTL1 = 0x8D;
DCOCTL = 0x88;
}
/*BCSCTL1 |= XT2OFF; // Make sure XT2 is off */
/* BCSCTL2 = 0x00; // MCLK = DCOCLK/1 */
/* SMCLK = DCOCLK/1 */
/* DCO Internal Resistor */
}
/*---------------------------------------------------------------------------*/
static void
init_ports(void)
{
/* Turn everything off, device drivers enable what is needed. */
/* All configured for digital I/O */
#ifdef P1SEL
P1SEL = 0;
#endif
#ifdef P2SEL
P2SEL = 0;
#endif
#ifdef P3SEL
P3SEL = 0;
#endif
#ifdef P4SEL
P4SEL = 0;
#endif
#ifdef P5SEL
P5SEL = 0;
#endif
#ifdef P6SEL
P6SEL = 0;
#endif
/* All available inputs */
#ifdef P1DIR
P1DIR = 0;
P1OUT = 0;
#endif
#ifdef P2DIR
P2DIR = 0;
P2OUT = 0;
#endif
#ifdef P3DIR
P3DIR = 0;
P3OUT = 0;
#endif
#ifdef P4DIR
P4DIR = 0;
P4OUT = 0;
#endif
#ifdef P5DIR
P5DIR = 0;
P5OUT = 0;
#endif
#ifdef P6DIR
P6DIR = 0;
P6OUT = 0;
#endif
#ifdef P7DIR
P7DIR = 0;
P7OUT = 0;
#endif
#ifdef P8DIR
P8DIR = 0;
P8OUT = 0;
#endif
P1IE = 0;
P2IE = 0;
}
/*---------------------------------------------------------------------------*/
/* msp430-ld may align _end incorrectly. Workaround in cpu_init. */
#if defined(__MSP430__) && defined(__GNUC__)
extern int _end; /* Not in sys/unistd.h */
static char *cur_break = (char *)&_end;
#endif
void
msp430_cpu_init(void)
{
dint();
watchdog_init();
init_ports();
/* set DCO to a reasonable default value (8MHz) */
msp430_init_dco();
/* calibrate the DCO step-by-step */
msp430_sync_dco();
eint();
#if defined(__MSP430__) && defined(__GNUC__)
if((uintptr_t)cur_break & 1) { /* Workaround for msp430-ld bug! */
cur_break++;
}
#endif
}
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*
* Mask all interrupts that can be masked.
*/
int
splhigh_(void)
{
/* Clear the GIE (General Interrupt Enable) flag. */
int sr;
#ifdef __IAR_SYSTEMS_ICC__
sr = __get_SR_register();
__bic_SR_register(GIE);
#else
asmv("mov r2, %0" : "=r" (sr));
asmv("bic %0, r2" : : "i" (GIE));
#endif
return sr & GIE; /* Ignore other sr bits. */
}
/*---------------------------------------------------------------------------*/
/*
* Restore previous interrupt mask.
*/
/* void */
/* splx_(int sr) */
/* { */
/* #ifdef __IAR_SYSTEMS_ICC__ */
/* __bis_SR_register(sr); */
/* #else */
/* /\* If GIE was set, restore it. *\/ */
/* asmv("bis %0, r2" : : "r" (sr)); */
/* #endif */
/* } */
/*---------------------------------------------------------------------------*/
#ifdef __IAR_SYSTEMS_ICC__
int __low_level_init(void)
{
/* turn off watchdog so that C-init will run */
WDTCTL = WDTPW + WDTHOLD;
/*
* Return value:
*
* 1 - Perform data segment initialization.
* 0 - Skip data segment initialization.
*/
return 1;
}
#endif
/*---------------------------------------------------------------------------*/
void
msp430_sync_dco(void) {
uint16_t oldcapture;
int16_t diff;
/* DELTA_2 assumes an ACLK of 32768 Hz */
#define DELTA_2 ((MSP430_CPU_SPEED) / 32768)
/* Select SMCLK clock, and capture on ACLK for TBCCR6 */
TBCTL = TBSSEL1 | TBCLR;
TBCCTL6 = CCIS0 + CM0 + CAP;
/* start the timer */
TBCTL |= MC1;
while(1) {
/* wait for the next capture */
TBCCTL6 &= ~CCIFG;
while(!(TBCCTL6 & CCIFG));
oldcapture = TBCCR6;
/* wait for the next capture - and calculate difference */
TBCCTL6 &= ~CCIFG;
while(!(TBCCTL6 & CCIFG));
diff = TBCCR6 - oldcapture;
/* resynchronize the DCO speed if not at target */
if(DELTA_2 == diff) {
break; /* if equal, leave "while(1)" */
} else if(DELTA_2 < diff) { /* DCO is too fast, slow it down */
DCOCTL--;
if(DCOCTL == 0xFF) { /* Did DCO roll under? */
BCSCTL1--;
}
} else { /* -> Select next lower RSEL */
DCOCTL++;
if(DCOCTL == 0x00) { /* Did DCO roll over? */
BCSCTL1++;
}
/* -> Select next higher RSEL */
}
}
/* Stop the timer - conserves energy according to user guide */
TBCTL = 0;
}
/*---------------------------------------------------------------------------*/