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.
This commit is contained in:
parent
77ef1f08b2
commit
68b65e6c47
|
@ -83,68 +83,6 @@ msp430_init_dco(void)
|
||||||
/* DCO Internal Resistor */
|
/* DCO Internal Resistor */
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
/* Start CPU with full speed (? good or bad?) and go downwards */
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
void
|
|
||||||
msp430_quick_synch_dco(void) {
|
|
||||||
uint16_t last;
|
|
||||||
uint16_t diff;
|
|
||||||
uint16_t dco_reg = 0x0fff;
|
|
||||||
uint8_t current_bit = 12;
|
|
||||||
uint16_t i;
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
BCSCTL1 = 0x8D | 7;
|
|
||||||
DCOCTL = 0xff; /* MAX SPEED ?? */
|
|
||||||
|
|
||||||
/* IDEA: do binary search - check MSB first, etc... */
|
|
||||||
/* 1 set current bit to zero - if to slow, put back to 1 */
|
|
||||||
while(current_bit--) {
|
|
||||||
/* first set the current bit to zero and check - we know that it is
|
|
||||||
set from start so ^ works (first bit = bit 11) */
|
|
||||||
dco_reg = dco_reg ^ (1 << current_bit); /* clear bit 11..10..9.. */
|
|
||||||
|
|
||||||
/* set dco registers */
|
|
||||||
DCOCTL = dco_reg & 0xff;
|
|
||||||
BCSCTL1 = (BCSCTL1 & 0xf8) | (dco_reg >> 8);
|
|
||||||
|
|
||||||
/* some delay to make clock stable - could possibly be made using
|
|
||||||
captures too ... */
|
|
||||||
for(i=0; i < 1000; i++) {
|
|
||||||
i = i | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* do capture... */
|
|
||||||
while(!(TBCCTL6 & CCIFG));
|
|
||||||
last = TBCCR6;
|
|
||||||
|
|
||||||
TBCCTL6 &= ~CCIFG;
|
|
||||||
/* wait for next Capture - and calculate difference */
|
|
||||||
while(!(TBCCTL6 & CCIFG));
|
|
||||||
diff = TBCCR6 - last;
|
|
||||||
|
|
||||||
/* /\* store what was run during the specific test *\/ */
|
|
||||||
/* dcos[current_bit] = dco_reg; */
|
|
||||||
/* vals[current_bit] = diff; */
|
|
||||||
|
|
||||||
/* should we keep the bit cleared or not ? */
|
|
||||||
if(diff < DELTA_2) { /* DCO is too slow - fewer ticks than desired */
|
|
||||||
/* toggle bit again to get it back to one */
|
|
||||||
dco_reg = dco_reg ^ (1 << current_bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
static void
|
static void
|
||||||
init_ports(void)
|
init_ports(void)
|
||||||
{
|
{
|
||||||
|
@ -224,7 +162,10 @@ msp430_cpu_init(void)
|
||||||
dint();
|
dint();
|
||||||
watchdog_init();
|
watchdog_init();
|
||||||
init_ports();
|
init_ports();
|
||||||
msp430_quick_synch_dco();
|
/* set DCO to a reasonable default value (8MHz) */
|
||||||
|
msp430_init_dco();
|
||||||
|
/* calibrate the DCO step-by-step */
|
||||||
|
msp430_sync_dco();
|
||||||
eint();
|
eint();
|
||||||
#if defined(__MSP430__) && defined(__GNUC__)
|
#if defined(__MSP430__) && defined(__GNUC__)
|
||||||
if((uintptr_t)cur_break & 1) { /* Workaround for msp430-ld bug! */
|
if((uintptr_t)cur_break & 1) { /* Workaround for msp430-ld bug! */
|
||||||
|
@ -282,13 +223,10 @@ int __low_level_init(void)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
#if DCOSYNCH_CONF_ENABLED
|
|
||||||
/* this code will always start the TimerB if not already started */
|
|
||||||
void
|
void
|
||||||
msp430_sync_dco(void) {
|
msp430_sync_dco(void) {
|
||||||
uint16_t last;
|
uint16_t oldcapture;
|
||||||
uint16_t diff;
|
int16_t diff;
|
||||||
/* uint32_t speed; */
|
|
||||||
/* DELTA_2 assumes an ACLK of 32768 Hz */
|
/* DELTA_2 assumes an ACLK of 32768 Hz */
|
||||||
#define DELTA_2 ((MSP430_CPU_SPEED) / 32768)
|
#define DELTA_2 ((MSP430_CPU_SPEED) / 32768)
|
||||||
|
|
||||||
|
@ -298,36 +236,35 @@ msp430_sync_dco(void) {
|
||||||
/* start the timer */
|
/* start the timer */
|
||||||
TBCTL |= MC1;
|
TBCTL |= MC1;
|
||||||
|
|
||||||
/* wait for next Capture */
|
while(1) {
|
||||||
TBCCTL6 &= ~CCIFG;
|
/* wait for the next capture */
|
||||||
while(!(TBCCTL6 & CCIFG));
|
TBCCTL6 &= ~CCIFG;
|
||||||
last = TBCCR6;
|
while(!(TBCCTL6 & CCIFG));
|
||||||
|
oldcapture = TBCCR6;
|
||||||
|
|
||||||
TBCCTL6 &= ~CCIFG;
|
/* wait for the next capture - and calculate difference */
|
||||||
/* wait for next Capture - and calculate difference */
|
TBCCTL6 &= ~CCIFG;
|
||||||
while(!(TBCCTL6 & CCIFG));
|
while(!(TBCCTL6 & CCIFG));
|
||||||
diff = TBCCR6 - last;
|
diff = TBCCR6 - oldcapture;
|
||||||
|
|
||||||
/* Stop timer - conserves energy according to user guide */
|
/* resynchronize the DCO speed if not at target */
|
||||||
TBCTL = 0;
|
if(DELTA_2 == diff) {
|
||||||
|
break; /* if equal, leave "while(1)" */
|
||||||
/* speed = diff; */
|
} else if(DELTA_2 < diff) { /* DCO is too fast, slow it down */
|
||||||
/* speed = speed * 32768; */
|
DCOCTL--;
|
||||||
/* printf("Last TAR diff:%d target: %ld ", diff, DELTA_2); */
|
if(DCOCTL == 0xFF) { /* Did DCO roll under? */
|
||||||
/* printf("CPU Speed: %lu DCOCTL: %d\n", speed, DCOCTL); */
|
BCSCTL1--;
|
||||||
|
}
|
||||||
/* resynchronize the DCO speed if not at target */
|
} else { /* -> Select next lower RSEL */
|
||||||
if(DELTA_2 < diff) { /* DCO is too fast, slow it down */
|
DCOCTL++;
|
||||||
DCOCTL--;
|
if(DCOCTL == 0x00) { /* Did DCO roll over? */
|
||||||
if(DCOCTL == 0xFF) { /* Did DCO role under? */
|
BCSCTL1++;
|
||||||
BCSCTL1--;
|
}
|
||||||
}
|
/* -> Select next higher RSEL */
|
||||||
} else if(DELTA_2 > diff) {
|
|
||||||
DCOCTL++;
|
|
||||||
if(DCOCTL == 0x00) { /* Did DCO role over? */
|
|
||||||
BCSCTL1++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Stop the timer - conserves energy according to user guide */
|
||||||
|
TBCTL = 0;
|
||||||
}
|
}
|
||||||
#endif /* DCOSYNCH_CONF_ENABLED */
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
|
Loading…
Reference in a new issue