我有一个我认为最适合 ATMega328P 的项目。然而,在我见过的每一个简单的项目中,人们总是连接一个 16MHz 的外部振荡器。据我所知,它应该有一个 8MHz 的内部振荡器。我的项目不需要很多处理能力,时间也不需要非常精确(UART 和 I2C 除外)。我也有一个程序员,所以我不需要担心引导加载程序。
我有什么理由使用外部振荡器吗?
我有一个我认为最适合 ATMega328P 的项目。然而,在我见过的每一个简单的项目中,人们总是连接一个 16MHz 的外部振荡器。据我所知,它应该有一个 8MHz 的内部振荡器。我的项目不需要很多处理能力,时间也不需要非常精确(UART 和 I2C 除外)。我也有一个程序员,所以我不需要担心引导加载程序。
我有什么理由使用外部振荡器吗?
你不说的是这个内部振荡器的精度是多少。我花了一些时间在第 369 页 的数据表中找到它。
10%。百分之十!那对于校准的振荡器呢?这太可怕了。期望 this 的误差低至1%并非不合理。Microchip/Atmel 提供了一份用于自行校准振荡器以达到 1% 精度的文档。
I2C是一种同步协议,只要遵守最小和最大脉冲时间,时间精度就无关紧要。另一方面,
UART是异步的,因此时序准确性确实很重要。大多数 UART 允许在最后一位(停止位)中出现半位错误,因此对于 10 位传输来说这是 5%。
工厂校准的振荡器不会在这里做。您必须通过校准程序才能达到 1%。在这种情况下,您可以使用内部振荡器。否则你将不得不使用水晶。
当您使用 UART 时,建议使用晶体振荡器。如果不是这样,您可以使用内部振荡器。一些 MCU 具有工厂调整的内部振荡器,可适用于 UART 操作。
“时间不敏感”。UART 对时间非常敏感。如果没有正确同步,您将得到完全的垃圾。
选项1:使用普通水晶。适当更改时钟选择保险丝。水晶的选择取决于你想使用什么波特/你想让这个东西走多快。有“魔法水晶”会给你标准费率的 0% 误差(如果它们制造得很好)。有关更多信息,请参见第 20 节 [USART0] 中的表格(您已阅读数据表……对???):)。
选项 2:如果担心功率问题,您可以使用 32khz 晶振校准内部振荡器。使用 32khz,您可以在睡眠模式下获得 uA 电流(我已将其降至 ~2uA)。您必须设置一个校准例程,其中涉及启动/停止计时器以及将 timer2 交替到异步模式。
328P 代码可能有所不同...此功能目前适用于 48/88(具有适当的 F_CPU/波特率定义。它有点难看/没有完全重构,但我学到了比在你工作时搞砸的东西更好在最后期限。在 AVRFreaks 论坛上搜索“调谐 32khz 水晶”之类的东西。这只是对你将要进入的内容的一种品味......不一定会起作用。
char OSCCAL_calibration(char starting_cal, int cal_value){
//Function calibrates the internal oscillator so usart comms go through.
//Works by continually checking two different timers:
// (0 -> tied to internal, and 2 -> async to crystal).
// Recommended cal_value = 5900 for the crystals we're using.
// Must be running 8MHZ with clkdiv8 fuse enabled.
// TODO: Make sure to check all the math on this later.
unsigned char calibrate = FALSE;
int temp;
unsigned char tempL;
volatile char osccal_temp=starting_cal;
int cal_bandwidth = 50;
//int cal_value = 6250;
//int cal_value = 5900; //Works. Need to find out why.
//Dont use clock prescalers. We're already div8ing.
//CLKPR = (1<<CLKPCE); // set Clock Prescaler Change Enable
// set prescaler = 8, Inter RC 8Mhz / 8 = 1Mhz
//CLKPR = (1<<CLKPS1) | (1<<CLKPS0);
TIMSK2 = 0; //disable OCIE2A and TOIE2
ASSR = (1<<AS2); //select asynchronous operation of timer2 (32,768kHz)
OCR2B = 200; // set timer2 compare value. We probably only need to compare A
OCR2A = 200;
TIMSK0 = 0; // delete any interrupt sources
TCCR2A = (1<<WGM21); //Normal operation. Reset timer on hitting TOP (ocr2a).
TCCR2B = (1<<CS20); // start timer2 with no prescaling
TCCR1B = (1<<CS10); // start timer1 with no prescaling
//wait for everythnig to mellow out.
while((ASSR & (1<<TCN2UB)) | (ASSR & (1<<OCR2BUB)) | (ASSR & (1<<TCR2BUB)) | (ASSR & (1<<OCR2AUB)) | (ASSR & (TCR2AUB))); //wait for TCN2UB and TCR2UB to be cleared
//This is specifically for the crystal to stabilize. Check for better times.
_delay_ms(1000);
while(!calibrate){
cli(); // disable global interrupt
TIFR1 = 0xFF; // delete TIFR1 flags
TIFR2 = 0xFF; // delete TIFR2 flags
TCNT1H = 0; // clear timer1 counter
TCNT1L = 0;
TCNT2 = 0; // clear timer2 counter
//Stop timer on compare match.
while ( !(TIFR2 & (1<<OCF2A)) );
TCCR1B = 0;
//Check for overflows (useless if it happens).
sei();
if ( (TIFR1 & (1<<TOV1)) ){
temp = 0xFFFF; // if timer1 overflows, set the temp to 0xFFFF
}else{ // read out the timer1 counter value
tempL = TCNT1L;
temp = TCNT1H;
temp = (temp << 8);
temp += tempL;
}
//Check timer value against calculated value.
if (temp > (cal_value+(cal_bandwidth/2))){
//Oscillator is too fast.
osccal_temp--;
OSCCAL=osccal_temp;
}else if (temp < (cal_value-(cal_bandwidth/2))){
//Oscillator is too slow.
osccal_temp++;
OSCCAL=osccal_temp;
}else{
//Just right.
calibrate = TRUE;
}
TCCR1B = (1<<CS10); // start timer1
}
//TODO: Stop timers, ya?
//Now setup timer2 to run "normally" aka async+interrupts.
//Disable interrupt source. Set mask. Wait for registers to clear.
TIFR2 = (1<<TOV2);
TIMSK2 = (1<<TOIE2);
ASSR = (1<<AS2); //select asynchronous operation of timer2 (32,768kHz)
TIMSK0 = 0; // delete any interrupt sources
//Normal Op. 256 prescale.
TCCR2A = 0x00;
TCCR2B = (1<<CS22) | (1<<CS21);
TCCR1B = 0x00; // turn off timer1
//wait for everythnig to mellow out.
while((ASSR & (1<<TCN2UB)) | (ASSR & (1<<OCR2BUB)) | (ASSR & (1<<TCR2BUB)) | (ASSR & (1<<OCR2AUB)) | (ASSR & (TCR2AUB))); //wait for TCN2UB and TCR2UB to be cleared
//This is specifically for the crystal to stabilize. Check for better times.
_delay_ms(1000);
return osccal_temp;
}
还应注意,晶体需要很长时间才能启动。这实际上是因为它的精度:它只从非常窄的频带中获取能量。对于电池供电的东西来说,这可能是一种负担,在这种情况下,您会时不时地在很短的时间内唤醒 MCU:在全功率消耗下等待一毫秒以使晶体启动是净损失。陶瓷谐振器比内部 RC 振荡器更准确,但不如晶体,并相应地启动。
当然,16MHz atmega 比 8MHz atmega 喝更多的果汁并且需要更高的电压,但是可以使用 8MHz(或更低,低至 32kHz)晶体;这种选择也可以节省能源。