diff options
Diffstat (limited to 'firmware/libs-device')
-rw-r--r-- | firmware/libs-device/osccal.c | 178 | ||||
-rw-r--r-- | firmware/libs-device/osccal.h | 34 | ||||
-rw-r--r-- | firmware/libs-device/osccalASM.S | 228 |
3 files changed, 390 insertions, 50 deletions
diff --git a/firmware/libs-device/osccal.c b/firmware/libs-device/osccal.c index 939d5c3..8debe49 100644 --- a/firmware/libs-device/osccal.c +++ b/firmware/libs-device/osccal.c @@ -8,49 +8,169 @@ */ #include <avr/io.h> - +#include <avr/interrupt.h> #ifndef uchar #define uchar unsigned char #endif +int usbMeasureFrameLengthDecreasing(int); + /* ------------------------------------------------------------------------- */ /* ------------------------ Oscillator Calibration ------------------------- */ /* ------------------------------------------------------------------------- */ /* Calibrate the RC oscillator. Our timing reference is the Start Of Frame * signal (a single SE0 bit) repeating every millisecond immediately after - * a USB RESET. We first do a binary search for the OSCCAL value and then - * optimize this value with a neighboorhod search. + * a USB RESET. + * + * + * Optimized version by cpldcpu@gmail.com, Nov 3rd 2013. + * + * Benefits: + * - Codesize reduced by 54 bytes. + * - Improved robustness due to removing timeout from frame length measurement and + * inserted NOP after OSCCAL writes. + * + * Changes: + * - The new routine performs a combined binary and neighborhood search + * in a single loop. + * Note that the neighborhood search is necessary due to the quasi-monotonic + * nature of OSCCAL. (See Atmel application note AVR054). + * - Inserted NOP after writes to OSCCAL to avoid CPU errors during oscillator + * stabilization. + * - Implemented new routine to measure frame time "usbMeasureFrameLengthDecreasing". + * This routine takes the target time as a parameter and returns the deviation. + * - usbMeasureFrameLengthDecreasing measures in multiples of 5 cycles and is thus + * slighly more accurate. + * - usbMeasureFrameLengthDecreasing does not support time out anymore. The original + * implementation returned zero in case of time out, which would have caused the old + * calibrateOscillator() implementation to increase OSSCAL to 255, effictively + * overclocking and most likely crashing the CPU. The new implementation will enter + * an infinite loop when no USB activity is encountered. The user program should + * use the watchdog to escape from situations like this. + * + * This routine will work both on controllers with and without split OSCCAL range. + * The first trial value is 128 which is the lowest value of the upper OSCCAL range + * on Attiny85 and will effectively limit the search to the upper range, unless the + * RC oscillator frequency is unusually high. Under normal operation, the highest + * tested frequency setting is 192. This corresponds to ~20 Mhz core frequency and + * is still within spec for a 5V device. */ + void calibrateOscillator(void) { -uchar step = 128; -uchar trialValue = 0, optimumValue; -int x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5); - - /* do a binary search: */ - do{ - OSCCAL = trialValue + step; - x = usbMeasureFrameLength(); /* proportional to current real frequency */ - if(x < targetValue) /* frequency still too low */ - trialValue += step; - step >>= 1; - }while(step > 0); - /* We have a precision of +/- 1 for optimum OSCCAL here */ - /* now do a neighborhood search for optimum value */ - optimumValue = trialValue; - optimumDev = x; /* this is certainly far away from optimum */ - for(OSCCAL = trialValue - 1; OSCCAL <= trialValue + 1; OSCCAL++){ - x = usbMeasureFrameLength() - targetValue; - if(x < 0) - x = -x; - if(x < optimumDev){ - optimumDev = x; - optimumValue = OSCCAL; - } - } - OSCCAL = optimumValue; + uchar step, trialValue, optimumValue; + int x, targetValue; + uchar optimumDev; + uchar i,xl; + + targetValue = (unsigned)((double)F_CPU * 999e-6 / 5.0 + 0.5); /* Time is measured in multiples of 5 cycles. Target is 0.999µs */ + optimumDev = 0xff; + // optimumValue = OSCCAL; + step=64; + trialValue = 128; + + cli(); // disable interrupts + + /* + Performs seven iterations of a binary search (stepwidth decreasing9 + with three additional steps of a neighborhood search (step=1, trialvalue will oscillate around target value to find optimum) + */ + + for(i=0; i<10; i++){ + OSCCAL = trialValue; + asm volatile(" NOP"); + + x = usbMeasureFrameLengthDecreasing(targetValue); + + if(x < 0) /* frequency too high */ + { + trialValue -= step; + xl=(uchar)-x; + } + else /* frequency too low */ + { + trialValue += step; + xl=(uchar)x; + } + + /* + Halve stepwidth to perform binary search. Logical oring with 1 to ensure step is never equal to zero. + This results in a neighborhood search with stepwidth 1 after binary search is finished. + Once the neighbourhood search stage is reached, x will be smaller than +-255, hence more code can be + saved by only working with the lower 8 bits. + */ + + step >>= 1; + + if (step==0) + { + step=1; + if(xl <= optimumDev){ + optimumDev = xl; + optimumValue = OSCCAL; + } + } + + } + + OSCCAL = optimumValue; + asm volatile(" NOP"); + + sei(); // enable interrupts } + +void calibrateOscillator_old(void) +{ + uchar step, trialValue, optimumValue; + int x, optimumDev, targetValue; + uchar i; + + targetValue = (unsigned)((double)F_CPU * 999e-6 / 5.0 + 0.5); /* Time is measured in multiples of 5 cycles. Target is 0.999µs */ + optimumDev = 0x7f00; // set to high positive value + optimumValue = OSCCAL; + step=64; + trialValue = 128; + + /* + Performs seven iterations of a binary search (stepwidth decreasing9 + with three additional steps of a neighborhood search (step=1, trialvalue will oscillate around target value to find optimum) + */ + + for(i=0; i<10; i++){ + OSCCAL = trialValue; + asm volatile(" NOP"); + + x = usbMeasureFrameLengthDecreasing(targetValue); + + if(x < 0) /* frequency too high */ + { + trialValue -= step; + x = -x; + } + else /* frequency too low */ + { + trialValue += step; + } + + /* + Halve stepwidth to perform binary search. Logical oring with 1 to ensure step is never equal to zero. + This results in a neighborhood search with stepwidth 1 after binary search is finished. + */ + + step >>= 1; + step |=1; + + if(x < optimumDev){ + optimumDev = x; + optimumValue = OSCCAL; + } + } + + OSCCAL = optimumValue; + asm volatile(" NOP"); +} + /* Note: This calibration algorithm may try OSCCAL values of up to 192 even if the optimum value is far below 192. It may therefore exceed the allowed clock diff --git a/firmware/libs-device/osccal.h b/firmware/libs-device/osccal.h index 710ce05..af37a43 100644 --- a/firmware/libs-device/osccal.h +++ b/firmware/libs-device/osccal.h @@ -1,10 +1,10 @@ /* Name: osccal.h * Author: Christian Starkjohann * Creation Date: 2008-04-10 + * Changes 2013-11-04 cpldcpu@gmail.com * Tabsize: 4 * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt) - * This Revision: $Id: osccal.h 762 2009-08-12 17:10:30Z cs $ */ /* @@ -14,30 +14,18 @@ oscillator so that the CPU runs at F_CPU (F_CPU is a macro which must be defined when the module is compiled, best passed in the compiler command line). The time reference is the USB frame clock of 1 kHz available immediately after a USB RESET condition. Timing is done by counting CPU -cycles, so all interrupts must be disabled while the calibration runs. For -low level timing measurements, usbMeasureFrameLength() is called. This -function must be enabled in usbconfig.h by defining -USB_CFG_HAVE_MEASURE_FRAME_LENGTH to 1. It is recommended to call -calibrateOscillator() from the reset hook in usbconfig.h: -*/ +cycles, so all interrupts must be disabled while the calibration runs. +The size optimized assembler implementation includes its own implementation +of usbMeasureFrameLength. Therefore USB_CFG_HAVE_MEASURE_FRAME_LENGTH should +be set to 0 to avoid including unused code sections. It is recommended to call +calibrateOscillatorASM() from the reset hook in usbconfig.h by including osccal.h: -#ifndef __ASSEMBLER__ -#include <avr/interrupt.h> // for sei() -extern void calibrateOscillator(void); -#endif -#define USB_RESET_HOOK(resetStarts) if(!resetStarts){cli(); calibrateOscillator(); sei();} +#include "osccal.h" -/* This routine is an alternative to the continuous synchronization described in osctune.h. -Algorithm used: -calibrateOscillator() first does a binary search in the OSCCAL register for -the best matching oscillator frequency. Then it does a next neighbor search -to find the value with the lowest clock rate deviation. It is guaranteed to -find the best match among neighboring values, but for version 5 oscillators -(which have a discontinuous relationship between OSCCAL and frequency) a -better match might be available in another OSCCAL region. +Algorithm used: See osccalASM.x Limitations: This calibration algorithm may try OSCCAL values of up to 192 even if the @@ -53,7 +41,11 @@ deviation! All other frequency modules require at least 0.2% precision. #ifndef __OSCCAL_H_INCLUDED__ #define __OSCCAL_H_INCLUDED__ -//void calibrateOscillator(void); +#ifndef __ASSEMBLER__ + void calibrateOscillatorASM(void); +# define USB_RESET_HOOK(resetStarts) if(!resetStarts){ calibrateOscillatorASM();} +# define USB_CFG_HAVE_MEASURE_FRAME_LENGTH 0 +#endif /* This function calibrates the RC oscillator so that the CPU runs at F_CPU. * It MUST be called immediately after the end of a USB RESET condition! * Disable all interrupts during the call! diff --git a/firmware/libs-device/osccalASM.S b/firmware/libs-device/osccalASM.S new file mode 100644 index 0000000..9a317f1 --- /dev/null +++ b/firmware/libs-device/osccalASM.S @@ -0,0 +1,228 @@ +/* Name: osccalASM.S + * Author: cpldcpu@gmail.com + * Creation Date: 2013-11-3 + * Tabsize: 4 + * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt) + */ + +/* Calibrate the RC oscillator. Our timing reference is the Start Of Frame + * signal (a single SE0 bit) repeating every millisecond immediately after + * a USB RESET. + * + * + * Benefits: + * - Codesize reduced by 90 bytes. + * - Improved robustness due to removing timeout from frame length measurement and + * inserted NOP after OSCCAL writes. + * + * Changes: + * - The new routine performs a combined binary and neighborhood search + * in a single loop. + * Note that the neighborhood search is necessary due to the quasi-monotonic + * nature of OSCCAL. (See Atmel application note AVR054). + * - Inserted NOP after writes to OSCCAL to avoid CPU errors during oscillator + * stabilization. + * - Implemented new routine to measure frame time "usbMeasureFrameLengthDecreasing". + * This routine takes the target time as a parameter and returns the deviation. + * - usbMeasureFrameLengthDecreasing measures in multiples of 5 cycles and is thus + * slighly more accurate. + * - usbMeasureFrameLengthDecreasing does not support time out anymore. The original + * implementation returned zero in case of time out, which would have caused the old + * calibrateOscillator() implementation to increase OSSCAL to 255, effictively + * overclocking and most likely crashing the CPU. The new implementation will enter + * an infinite loop when no USB activity is encountered. The user program should + * use the watchdog to escape from situations like this. + * + * This routine will work both on controllers with and without split OSCCAL range. + * The first trial value is 128 which is the lowest value of the upper OSCCAL range + * on Attiny85 and will effectively limit the search to the upper range, unless the + * RC oscillator frequency is unusually high. Under normal operation, the highest + * tested frequency setting is 192. This corresponds to ~20 Mhz core frequency and + * is still within spec for a 5V device. + */ + + +#define __SFR_OFFSET 0 /* used by avr-libc's register definitions */ +#include "./usbdrv/usbdrv.h" /* for common defs */ + +#ifdef __IAR_SYSTEMS_ASM__ +/* Register assignments for usbMeasureFrameLengthDecreasing on IAR cc */ +/* Calling conventions on IAR: + * First parameter passed in r16/r17, second in r18/r19 and so on. + * Callee must preserve r4-r15, r24-r29 (r28/r29 is frame pointer) + * Result is passed in r16/r17 + * In case of the "tiny" memory model, pointers are only 8 bit with no + * padding. We therefore pass argument 1 as "16 bit unsigned". + */ + +//Untested + +# define i r20 +# define opV r19 +# define opD r18 +# define try r21 +# define stp r22 + +# define cnt16L r30 +# define cnt16H r31 + + +#else /* __IAR_SYSTEMS_ASM__ */ +/* Register assignments for usbMeasureFrameLength on gcc */ +/* Calling conventions on gcc: + * First parameter passed in r24/r25, second in r22/23 and so on. + * Callee must preserve r1-r17, r28/r29 + * Result is passed in r24/r25 + */ + +# define i r20 +# define opV r19 +# define opD r18 +# define try r27 +# define stp r26 +# define cnt16L r24 +# define cnt16H r25 +#endif +# define cnt16 cnt16L + +; extern void calibrateOscillatorASM(void); + +.global calibrateOscillatorASM +calibrateOscillatorASM: + + cli + ldi opD, 255 + + ldi try, 128 ; calibration start value + ldi stp, 64 ; initial step width + ldi i, 10 ; 10 iterations + +usbCOloop: + + out OSCCAL, try + nop + + ; Delay values = F_CPU * 999e-6 / 5 + 0.5 + +#if (F_CPU == 16500000) + ldi cnt16L, lo8(3297) + ldi cnt16H, hi8(3297) +#elif (F_CPU == 12800000) + ldi cnt16L, lo8(2557) + ldi cnt16H, hi8(2557) +#else + #error "calibrateOscillatorASM: no delayvalues defined for this F_CPU setting" +#endif + +usbCOWaitStrobe: ; first wait for D- == 0 (idle strobe) + sbic USBIN, USBMINUS ; + rjmp usbCOWaitStrobe ; +usbCOWaitIdle: ; then wait until idle again + sbis USBIN, USBMINUS ;1 wait for D- == 1 + rjmp usbCOWaitIdle ;2 +usbCOWaitLoop: + sbiw cnt16,1 ;[0] [5] + sbic USBIN, USBMINUS ;[2] + rjmp usbCOWaitLoop ;[3] + + sbrs cnt16H, 7 ;delay overflow? + rjmp usbCOclocktoolow + sub try, stp + neg cnt16L + rjmp usbCOclocktoohigh +usbCOclocktoolow: + add try, stp +usbCOclocktoohigh: + lsr stp + brne usbCOnoneighborhoodsearch + cp opD, cnt16L + brcs usbCOnoimprovement + in opV, OSCCAL + mov opD, cnt16L +usbCOnoimprovement: + ldi stp, 1 +usbCOnoneighborhoodsearch: + subi i, 1 + brne usbCOloop + + out OSCCAL, opV + nop + sei + ret + +#undef i +#undef opV +#undef opD +#undef try +#undef stp +#undef cnt16 +#undef cnt16L +#undef cnt16H + +/* ------------------------------------------------------------------------- */ +/* ------ Original C Implementation of improved calibrateOscillator -------- */ +/* ---------------------- for Reference only ----------------------------- */ +/* ------------------------------------------------------------------------- */ + +#if 0 +void calibrateOscillator(void) +{ + uchar step, trialValue, optimumValue; + int x, targetValue; + uchar optimumDev; + uchar i,xl; + + targetValue = (unsigned)((double)F_CPU * 999e-6 / 5.0 + 0.5); /* Time is measured in multiples of 5 cycles. Target is 0.999µs */ + optimumDev = 0xff; + // optimumValue = OSCCAL; + step=64; + trialValue = 128; + + cli(); // disable interrupts + + /* + Performs seven iterations of a binary search (stepwidth decreasing9 + with three additional steps of a neighborhood search (step=1, trialvalue will oscillate around target value to find optimum) + */ + + for(i=0; i<10; i++){ + OSCCAL = trialValue; + asm volatile(" NOP"); + + x = usbMeasureFrameLengthDecreasing(targetValue); + + if(x < 0) /* frequency too high */ + { + trialValue -= step; + xl=(uchar)-x; + } + else /* frequency too low */ + { + trialValue += step; + xl=(uchar)x; + } + + /* + Halve stepwidth to perform binary search. At step=1 the mode changes to neighbourhood search. + Once the neighbourhood search stage is reached, x will be smaller than +-255, hence more code can be + saved by only working with the lower 8 bits. + */ + + step >>= 1; + + if (step==0) // Enter neighborhood search mode + { + step=1; + if(xl <= optimumDev){ + optimumDev = xl; + optimumValue = OSCCAL; + } + } + } + + OSCCAL = optimumValue; + asm volatile(" NOP"); + + sei(); // enable interrupts +} +#endif
\ No newline at end of file |