Automatische Kalibrierung des internen RC-Oszillators
von Michael Gaus
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;
}
}