From 014a1e9f44229545cc5382f5b2e8e5ae1853fd0d Mon Sep 17 00:00:00 2001 From: Jonathan McDowell Date: Sat, 14 Jan 2017 01:07:09 +0000 Subject: [PATCH] Move to micronucleus assembly for OSCCAL calibration 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 | 4 +- libs-device/osccal.c | 64 ----------- libs-device/osccal.h | 30 +++--- libs-device/osccalASM.S | 228 ++++++++++++++++++++++++++++++++++++++++ usbconfig.h | 10 +- 5 files changed, 246 insertions(+), 90 deletions(-) delete mode 100644 libs-device/osccal.c create mode 100644 libs-device/osccalASM.S diff --git a/Makefile b/Makefile index 412d090..b2fa31e 100644 --- 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 index 61ae783..0000000 --- a/libs-device/osccal.c +++ /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 - -#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. -*/ diff --git a/libs-device/osccal.h b/libs-device/osccal.h index 1ed6006..3452aa7 100644 --- a/libs-device/osccal.h +++ b/libs-device/osccal.h @@ -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 // 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 index 0000000..60d6fb9 --- /dev/null +++ b/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 diff --git a/usbconfig.h b/usbconfig.h index 0b106db..ffc4bc7 100644 --- a/usbconfig.h +++ b/usbconfig.h @@ -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 // 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. */ -- 2.39.2