Automatische Kalibrierung des internen RC-Oszillators          

von Michael Gaus                     
      
Elektronik-Labor  Projekte  AVR  Sparrow  Contest          





Der interne RC-Oszillator des ATtiny13 kann laut Datenblatt trotz des Kalibrierbytes, das beim Reset automatisch in das OSCCAL-Register geladen wird, eine Toleranz von bis zu +-10% haben. Durch Nachjustieren des OSCCAL-Werts kann meist eine bessere Genauigkeit erreicht werden. Mit dieser Sparrow-App ist eine automatische Kalibrierung mithilfe der Audio-Verbindung zum Sparrow möglich. Der ermittelte OSCCAL-Wert wird im EEPROM an der letzten Adresse 0x3F gespeichert und steht somit in anderen Sparrow-Apps zur Verfügung. Wichtig ist, dass in den Fusebytes die Fuse EESAVE aktiv ist, da ansonsten beim Aufspielen einer anderen Sparrow-App der im EEPROM gespeicherte OSCCAL-Wert während des Programmiervorgangs gelöscht werden würde.

Beim Programmstart leuchten zunächst beide LEDs. Nun muss zuerst gewährleistet sein, dass keine Audio-Ausgabe Richtung Sparrow stattfindet. Durch Drücken von Taste S1 wird die Messung gestartet. Nun kann über den SoundUart http://tiny.systems/article/sparrowSoundUART.html ein passender Datenstrom gesendet werden, der für einen automatischen Abgleich von OSCCAL geeignet ist.
Dazu sind folgende Einstellungen erforderlich:
Baudrate 1000, Byte 0x55, invertierte Ausgabe.
Nun muss auf den Button "CheepIt" geklickt werden, damit der SoundUart das Byte sendet. Falls anschließend die grüne LED nicht blinkt, muss noch ein zweites mal auf den Button geklickt werden.

Ob der Abgleich funktioniert hat, wird folgendermaßen über die beiden LEDs angezeigt:

Abgleich erfolgreich:
Rote LED aus, grüne LED blinkt langsam mit 1 Hz, wobei die LED in der Einschaltphase mit einer Frequenz von 1 KHz bei einem Duty Cycle von 80%  moduliert wird. Dadurch kann mit einem Oszilloskop oder Logicanalyzer geprüft werden, wie genau der Abgleich war.

Abgleich fehlgeschlagen:
Rote LED an, grüne LED blinkt.
Bei schnellem Blinken (4 Hz) gab es einen Timerüberlauf während der Messung. Dies deutet darauf hin, dass die Einstellungen im SoundUart (Baudrate und Byte) nicht korrekt sind.
Bei langsamem Blinken (2 Hz) liegt der ermittelte OSCCAL-Wert nicht in der erwünschten Toleranz von ca. +/-4%. In diesem Fall einfach einen Reset am Sparrow ausführen und nochmal einen neuen Abgleich starten.

Um in einer Sparrow-App den RC-Oszillator abzugleichen, sollte zuerst geprüft werden, ob der gespeicherte Wert im EEPROM an Adresse 0x3F ungleich 0x00 und ungleich 0xFF ist. Wenn ja, dann kann OSCCAL mit diesem Wert beschrieben werden.

Die Firmware wurde mit der kostenlosen Evaluation-Version (bis 4 kBytes Codegröße) des C-Compilers CodeVision AVR V3.10 geschrieben. Die Taktfrequenz beträgt 1,2 MHz (Clock prescaler = 8). Mittels eines Timers des ATtiny13 werden die Zeiten der Low-Pulse im Datenstrom, der über den SoundUart generiert wird, ausgemessen. Das OSCCAL-Register wird dann solange verändert, bis die gemessene Zeit dem gewünschten Sollwert von 1ms entspricht.


Links:
C-Compiler CodeVisionAVR V3: http://www.hpinfotech.ro/cvavr_download.html

Download: Sparrow_AutoCal.zip
Direkt laden: http://tiny.systems/categorie/cheepit/KalibrierungRC.html
/*******************************************************
This program was created by the
CodeWizardAVR V3.10 Evaluation
Automatic Program Generator
© Copyright 1998-2014 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com

Chip type : ATtiny13
AVR Core Clock frequency: 1,200000 MHz
Memory model : Tiny
External RAM size : 0
Data Stack size : 16
*******************************************************/

#include <tiny13.h>
#include <delay.h>

#define PIN_KEY_S1 PINB.0
#define PULLUP_KEY_S1 PORTB.0
#define PIN_SOUND_L PINB.2
#define PULLUP_SOUND_L PORTB.2
#define P_LED1_GREEN PORTB.1
#define DP_LED1_GREEN DDRB.1
#define LED1_GREEN_ON P_LED1_GREEN = 1;
#define LED1_GREEN_OFF P_LED1_GREEN = 0;
#define P_LED2_RED PORTB.3
#define DP_LED2_RED DDRB.3
#define LED2_RED_ON P_LED2_RED = 1;
#define LED2_RED_OFF P_LED2_RED = 0;

#define DP_PWM DDRB.1


#define TIMER_START TCCR0B=(0<<WGM02) | (0<<CS02) | (1<<CS01) | (0<<CS00);
#define TIMER_STOP TCCR0B=0;
#define TIMER_CLEAR TCNT0=0;

//#define DEBUGMODE

volatile unsigned char measuredTime;
volatile bit bTimerRunning = 0;
volatile bit bMeasuringComplete = 0;
volatile bit bErrorOverflow = 0;

eeprom unsigned char EE_osccal @ 0x3F; // OSCCAL will be written to EEPROM at address 0x3F


// Pin change interrupt service routine
interrupt [PC_INT0] void pin_change_isr(void)
{
if(bTimerRunning)
{
TIMER_STOP;
measuredTime = TCNT0;
bTimerRunning = 0;
TIMER_CLEAR;
bMeasuringComplete = 1;
}
else
{
TIMER_START;
bTimerRunning = 1;
}

if(TIFR0 & (1 << TOV0))
{
bErrorOverflow = 1;
GIMSK=(0<<INT0) | (0<<PCIE);
}

GIFR=(1<<PCIF);
}



void initTimer_MeasureAudioFrequency(void)
{ // audio frames come every 1ms

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 150,000 kHz
// Mode: Normal top=0xFF
// OC0A output: Disconnected
// OC0B output: Disconnected
// Timer Period: 1,7067 ms
TCCR0A=(0<<COM0A1) | (0<<COM0A0) | (0<<COM0B1) | (0<<COM0B0) | (0<<WGM01) | (0<<WGM00);
//TCCR0B=(0<<WGM02) | (0<<CS02) | (1<<CS01) | (0<<CS00);
TCCR0B=0; // disable timer
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;
}



void initTimer_OutputFrequency(void)
{
DP_PWM = 1; // enable Timer output pin

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 150,000 kHz
// Mode: Fast PWM top=OCR0A
// OC0A output: Disconnected
// OC0B output: Non-Inverted PWM
// Timer Period: 1 ms
// Output Pulse(s):
// OC0B Period: 1 ms Width: 0,79866 ms
TCCR0A=(0<<COM0A1) | (0<<COM0A0) | (1<<COM0B1) | (0<<COM0B0) | (1<<WGM01) | (1<<WGM00);
TCCR0B=(1<<WGM02) | (0<<CS02) | (1<<CS01) | (0<<CS00);
TCNT0=0x00;
OCR0A=0x95;
OCR0B=0x77;
}


void initExtInt(void)
{
// External Interrupt(s) initialization
// INT0: Off
// Interrupt on any change on pins PCINT0-5: On
GIMSK=(0<<INT0) | (1<<PCIE);
MCUCR=(0<<ISC01) | (0<<ISC00);
PCMSK=(0<<PCINT5) | (0<<PCINT4) | (0<<PCINT3) | (1<<PCINT2) | (0<<PCINT1) | (0<<PCINT0);
GIFR=(0<<INTF0) | (1<<PCIF);
}



#ifdef DEBUGMODE

#define P_UART_TX P_LED2_RED
#define DP_UART_TX DP_LED2_RED
#define DEBUG_ARRAYSIZE 8
unsigned char debugTime[DEBUG_ARRAYSIZE];

void initUart(void)
{
P_UART_TX = 1;
DP_UART_TX = 1;
}

void sendUartChar(char c)
{
#define UART_DELAYTIME /*760*/ 800 // 1200 Bd
unsigned char i;

P_UART_TX = 0; // Startbit
delay_us(UART_DELAYTIME);
for(i = 0; i < 8; i++)
{
if(c & 0x01)
{
P_UART_TX = 1;
}
else
{
P_UART_TX = 0;
}
c >>= 1;
delay_us(UART_DELAYTIME);
}
P_UART_TX = 1; // Stopbit
delay_us(UART_DELAYTIME);
}


void sendUart(char *s)
{
while(*s)
{
sendUartChar(*s);
s++;
}
}

#endif //DEBUGMODE


void main(void)
{
#define OSCCAL_RANGE 0x20 // allowed range +- from factory loaded OSCCAL
unsigned char min, max, value, cnt;
#ifdef DEBUGMODE
unsigned char i = 0;
#endif

// Crystal Oscillator division factor: 8
#pragma optsize-
CLKPR=(1<<CLKPCE);
CLKPR=(0<<CLKPCE) | (0<<CLKPS3) | (0<<CLKPS2) | (1<<CLKPS1) | (1<<CLKPS0);
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif

// Analog Comparator initialization
// Analog Comparator: Off
// The Analog Comparator's positive input is
// connected to the AIN0 pin
// The Analog Comparator's negative input is
// connected to the AIN1 pin
ACSR=(1<<ACD) | (0<<ACBG) | (0<<ACO) | (0<<ACI) | (0<<ACIE) | (0<<ACIS1) | (0<<ACIS0);
ADCSRB=(0<<ACME);

initTimer_MeasureAudioFrequency();

PIN_KEY_S1 = 1;
PULLUP_SOUND_L = 1;
LED1_GREEN_ON;
DP_LED1_GREEN = 1;
delay_ms(1000);
LED2_RED_ON;
DP_LED2_RED = 1;

initExtInt();

while(PIN_KEY_S1); // wait until key pressed
while(PIN_SOUND_L == 0); // wait until audio idle (High)
LED1_GREEN_OFF;
GIFR=(1<<PCIF); // clear EXTINT flag

#asm("sei")

if(OSCCAL >= OSCCAL_RANGE)
min = OSCCAL - OSCCAL_RANGE;
else
min = 0;
if(OSCCAL <= (0x7F - OSCCAL_RANGE))
max = OSCCAL + OSCCAL_RANGE;
else
max = 0x7F;

cnt = 4;
do
{
value = (max - min) / 2 + min;
OSCCAL = value;
while(!bMeasuringComplete && !bErrorOverflow); // wait until Audio measuring complete ...
if(bErrorOverflow)
{
break;
}
cnt++;
if(cnt == 5)
{ // ignore measurement of startbits because of too high tolerances
cnt = 0;
continue;
}

#ifdef DEBUGMODE
if(i < DEBUG_ARRAYSIZE)
{
debugTime[i] = measuredTime;
i++;
}
#endif

if(measuredTime == 150)
{
break;
}
else if(measuredTime < 150)
{
min = value;
}
else
{
max = value;
}
bMeasuringComplete = 0;
}
while((max - min) > 1);

GIMSK=(0<<PCIE); // disable ExtInt

#ifdef DEBUGMODE
initUart();
sendUart("Time:");
for(i = 0; i < DEBUG_ARRAYSIZE; i++)
{
sendUartChar(debugTime[i]);
}
sendUart("OSCCAL:");
sendUartChar(OSCCAL);
#endif

delay_ms(100);
if(bErrorOverflow)
{
LED2_RED_ON;
while(1)
{ // timer overflow => red LED ON, green blinking fast (4 Hz)
LED1_GREEN_ON;
delay_ms(125);
LED1_GREEN_OFF;
delay_ms(125);
}
}
if( (measuredTime > (150+7)) || (measuredTime < (150-7)) )
{
LED2_RED_ON;
while(1)
{ // too high tolerance => red LED ON, green blinking slow (2 Hz)
LED1_GREEN_ON;
delay_ms(250);
LED1_GREEN_OFF;
delay_ms(250);
}
}

EE_osccal = OSCCAL;
TIMER_STOP;
TIMER_CLEAR;
initTimer_OutputFrequency();
LED2_RED_OFF; // success => red LED off
P_LED1_GREEN = 0;

while (1)
{ // blink green LED with 1Hz, modulated with 1 kHz @ 80% duty cycle
delay_ms(500);
DP_PWM = 0;
delay_ms(500);
DP_PWM = 1;
}
}





Elektronik-Labor  Projekte  AVR  Sparrow Contest