]> the.earth.li Git - energenie-attiny.git/blobdiff - libs-device/osccalASM.S
Move to micronucleus assembly for OSCCAL calibration
[energenie-attiny.git] / libs-device / osccalASM.S
diff --git a/libs-device/osccalASM.S b/libs-device/osccalASM.S
new file mode 100644 (file)
index 0000000..60d6fb9
--- /dev/null
@@ -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