Move to micronucleus assembly for OSCCAL calibration
authorJonathan McDowell <noodles@earth.li>
Sat, 14 Jan 2017 01:07:09 +0000 (01:07 +0000)
committerJonathan McDowell <noodles@earth.li>
Sat, 14 Jan 2017 01:07:09 +0000 (01:07 +0000)
Some issues have been observed with OSCCAL calibration on older Dell
hosts. The micronucleus firmware enumerates correctly, but the Riso
clone then fails. Move to the assembly calibration code from
micronucleus to try and improve the situation.

Imported from https://github.com/micronucleus/micronucleus.git,
commit 7a53ce9e539a18fb46f7dc8e759f14833b10cc0c (July 31st 2016).

Makefile
libs-device/osccal.c [deleted file]
libs-device/osccal.h
libs-device/osccalASM.S [new file with mode: 0644]
usbconfig.h

index 412d090..b2fa31e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,11 @@
 DEVICE = attiny85
 F_CPU = 16500000
 
-CFLAGS = -Iusbdrv -I.
+CFLAGS = -Iusbdrv -Ilibs-device -I.
 AVRCC = avr-gcc -Wall -Os -DF_CPU=$(F_CPU) $(CFLAGS) -mmcu=$(DEVICE)
 
 OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o \
-       libs-device/osccal.o main.o
+       libs-device/osccalASM.o main.o
 
 .c.o:
        $(AVRCC) -c $< -o $@
diff --git a/libs-device/osccal.c b/libs-device/osccal.c
deleted file mode 100644 (file)
index 61ae783..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/* Name: osccal.c
- * Author: Christian Starkjohann
- * Creation Date: 2008-04-10
- * Tabsize: 4
- * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
- * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
- */
-
-#include <avr/io.h>
-
-#include "usbdrv.h"
-
-#ifndef uchar
-#define uchar   unsigned char
-#endif
-
-/* ------------------------------------------------------------------------- */
-/* ------------------------ 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.
- */
-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;
-}
-/*
-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
-frequency of the CPU in low voltage designs!
-You may replace this search algorithm with any other algorithm you like if
-you have additional constraints such as a maximum CPU clock.
-For version 5.x RC oscillators (those with a split range of 2x128 steps, e.g.
-ATTiny25, ATTiny45, ATTiny85), it may be useful to search for the optimum in
-both regions.
-*/
index 1ed6006..3452aa7 100644 (file)
@@ -1,5 +1,6 @@
 /* Name: osccal.h
  * Author: Christian Starkjohann
+ * Changes 2013-11-04 cpldcpu@gmail.com
  * Creation Date: 2008-04-10
  * Tabsize: 4
  * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
@@ -13,28 +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.S
 
 Limitations:
 This calibration algorithm may try OSCCAL values of up to 192 even if the
@@ -50,7 +41,10 @@ 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();}
+#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/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
index 0b106db..ffc4bc7 100644 (file)
@@ -10,6 +10,8 @@
 #ifndef __usbconfig_h_included__
 #define __usbconfig_h_included__
 
+#include "osccal.h"
+
 /*
 General Description:
 This file is an example configuration (with inline documentation) for the USB
@@ -162,11 +164,7 @@ section at the end of this file).
  * proceed, do a return after doing your things. One possible application
  * (besides debugging) is to flash a status LED on each packet.
  */
-#ifndef __ASSEMBLER__
-#include <avr/interrupt.h>  // for sei()
-extern void calibrateOscillator(void);
-#endif
-#define USB_RESET_HOOK(resetStarts)  if(!resetStarts){cli(); calibrateOscillator(); sei();}
+/* #define USB_RESET_HOOK(resetStarts)     if(!resetStarts){hadUsbReset();} */
 /* This macro is a hook if you need to know when an USB RESET occurs. It has
  * one parameter which distinguishes between the start of RESET state and its
  * end.
@@ -207,7 +205,7 @@ extern void calibrateOscillator(void);
  * usbFunctionWrite(). Use the global usbCurrentDataToken and a static variable
  * for each control- and out-endpoint to check for duplicate packets.
  */
-#define USB_CFG_HAVE_MEASURE_FRAME_LENGTH   1
+#define USB_CFG_HAVE_MEASURE_FRAME_LENGTH   0
 /* define this macro to 1 if you want the function usbMeasureFrameLength()
  * compiled in. This function can be used to calibrate the AVR's RC oscillator.
  */