簡體   English   中英

Attiny85,在 timer0 上實現的微秒定時器不計算正確的時間

[英]Attiny85, microsecond timer implemented on timer0 does not count the correct time

有 Attiny85,內部時鍾源為 8 MHz。

我正在嘗試基於硬件定時器 timer0 實現微秒定時器。

我的邏輯是什么:由於時鍾頻率為 8 MHz,並且預分頻器關閉,一個時鍾周期的時間約為 0.1us(1/8000000)。 最初,定時器溢出並在經過0...255時引起中斷,耗時超過0.1us,不方便計算1μs。

為了解決這個問題,我想到了將定時器的初始值從 0 改為 245 的選項。事實證明,為了獲得中斷,你需要通過 10 個時鍾周期來 go,這需要大約 1us 的時間.

我加載了此代碼,但 Attiny LED 顯然在 5 秒內沒有切換,盡管代碼指示 1 秒(1000000us)。

代碼:

#include <avr/io.h>
#undef F_CPU
#define F_CPU 8000000UL
#include <avr/interrupt.h>


// Timer0 init
void timer0_Init() {
    cli();
    //SREG &= ~(1 << 7);
    // Enable interrupt for timer0 overflow
    TIMSK |= (1 << 1);
    // Enabled timer0 (not prescaler) - CS02..CS00 = 001
    TCCR0B = 0;
    TCCR0B |= (1 << 0);
    // Clear timer0 counter
    TCNT0 = 245;
    sei();
    //SREG |= (1 << 7);
}

// timer0 overflow interrupt
// 1us interval logic:
// MCU frequency = 8mHz (8000000Hz), not prescaler
// 1 tick = 1/8000000 = 100ns = 0.1us, counter up++ after 1 tick (0.1us)
// 1us timer = 10 tick's => 245..255
static unsigned long microsecondsTimer;
ISR(TIMER0_OVF_vect) {
    microsecondsTimer++;
    TCNT0 = 245;
}

// Millis
/*unsigned long timerMillis() {
   return microsecondsTimer / 1000;
}*/

void ledBlink() {
    static unsigned long blinkTimer;
    static int ledState;
    // 10000us = 0.01s
    // 1000000us = 1s
    if(microsecondsTimer - blinkTimer >= 1000000) {
        if(!ledState) {
            PORTB |= (1 << 3); // HIGH
            } else {
            PORTB &= ~(1 << 3); // LOW
        }
        ledState = !ledState;
        blinkTimer = microsecondsTimer;
    }
}


int main(void)
{
    
    // Set LED pin to OUTPUT mode
    DDRB |= (1 << 3);
    
    timer0_Init();

    while (1)
    {
        ledBlink();
    }
}

Attiny85 數據表

可能是什么錯誤? 我還沒有學會如何使用保險絲,所以我最初通過 Arduino IDE 以 8 MHz 加載了保險絲,之后我已經通過 AVRDUDE 和 Atmel Studio 下載了主代碼(不更改保險絲)。

還有一個問題,我應該在更新微秒計數器時檢查最大值嗎? 我知道在 Arduino 中,micro 和 millis 計數器在達到最大值時會被重置。 例如,如果我不清除 TimerMicrosecond 變量變量並且它超過了 unsigned long 的大小,它會崩潰嗎?

正如@ReAI 所指出的,您的 ISR 沒有足夠的時間運行。 您的 ISR 將花費超過 1 微秒的時間來執行和返回,因此您總是會丟失中斷。

這里還有其他問題。 例如,您的microsecondsTimer變量在 ISR 和前台都被訪問,並且是一個long long變量有 4 個字節寬,因此不會自動更新。 例如,您的前台可能會開始讀取microsecondsTimer的值,然后在讀取過程中,ISR 可能會更新一些未讀字節,然后當前台再次啟動時,它會以損壞的結尾結束價值。 此外,您應該避免弄亂計數寄存器,因為除非您非常小心,否則更新它可能會錯過滴答聲。

那么如何實現一個有效的 uSec 計時器呢? 首先,您希望盡可能不頻繁地調用 ISR,所以也許選擇最大的預分頻器,您可以獲得所需的分辨率,並且只有 ISR 溢出。 在 ATTINY85 Timer0 的情況下,您可以選擇/8預分頻器,它在 8Mhz 系統時鍾下每微秒為您提供一個計時器滴答聲。 現在您的 ISR 每 256 微秒只運行一次,當它運行時,它只需要在每次調用中增加一個“微秒 * 256”計數器。

現在要在前台讀取當前的微秒數,您可以通過直接讀取計數寄存器來獲得microseconds mod 256 ,然后讀取“微秒 * 256”計數器並將其乘以 256 並加上該計數器,您將有完整的計數。 請注意,您需要采取特殊的預防措施以確保您的讀取是原子的。 您可以通過仔細關閉中斷,快速讀取值,然后重新打開中斷(保存中斷重新打開時的所有數學)來執行此操作,或者循環讀取值以確保您獲得兩個完整的連續讀取相同的內容(時間意味着在您閱讀它們時尚未更新)。

請注意,您可以查看Arduino 定時器 ISR的源代碼以獲取一些見解,但請注意,它們更復雜,因為它可以處理各種滴答速度,而您可以通過專門選擇 1us 周期來保持簡單。

為什么你不使用預分頻器?!

您的代碼需要一個非常大的延遲間隔(1 秒,根據 cpu 速度,這是很長的時間)......所以選擇每 1 我們中斷一次微控制器是不明智的......所以如果我們能減慢你的速度,那就太好了微控制器時鍾並例如每 1 ms 中斷一次

計算

微控制器時鍾速度為8 mega Hz ,因此如果我們將 preScaller 選擇為64 ,那么定時器時鍾將為8MHz/64 = 125 KHz ,這意味着每個 tik(定時器時鍾)時間將為1/125KHZ = 8 us

所以如果我們希望每1ms有一次 inturrpt 那么我們需要125 tik

修改代碼

試試這段代碼更容易理解



    #undef F_CPU
    #define F_CPU 8000000UL
    
    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    volatile int millSec;
    
    void timer0_Init();
    void toggleLed();
    
    int main(void)
    {
        
        // Set LED pin to OUTPUT mode
        DDRB |= (1 << 3);
        
        timer0_Init();
        millSec = 0;  // init the millsecond
        
        sei(); // set Global Interrupt Enable 
    
        while (1)
        {
            if(millSec >= 1000){
                // this block of code will run every 1 sec          
                millSec =0; // start count for the new sec
                toggleLed(); // just toggle the led state           
            }
            // Do other backGround jobs
        }
    }
    
    
   //#####Helper functions###########
    
    void timer0_Init() {
        
        // Clear timer0 counter
        TCNT0 = 130;  //255-125=130
        
        // Enable interrupt for timer0 overflow
        TIMSK = (1 << 1);
        
        // set  prescaler to 64 and start the timer
        TCCR0B = (1<<CS00)|(1<<CS01);
        
        
        
    }
    
    void toggleLed(){
        PORTB ^= (1 << 3); // toggle led output
    }
    
    ISR(TIMER0_OVF_vect) {
        // this interrupt will happen every 1 ms
        millSec++;
        // Clear timer0 counter
        TCNT0 = 130;
    }

對不起,我遲到了,但我有一些建議。 如果使用預分頻器 1 計算 Timer0,則定時器每 125ns 計數一次。 如果沒有小的分歧,就不可能達到 1 us。 但是,如果您使用預分頻器 8,您將達到 1 us。 我實際上沒有你的硬件,但試試看:

#ifndef F_CPU
  #define F_CPU 8000000UL
#else
  #error "F_CPU already defined"
#endif

#include <avr/io.h>
#include <avr/interrupt.h>

volatile unsigned int microsecondsTimer;

// Interrupt for Timer0 Compare Match A
ISR(TIMER0_COMPA_vect)
{
    microsecondsTimer++;
}

// Timer0 init
void timer0_Init()
{
  // Timer0:
  // - Mode: CTC
  // - Prescaler: /8

  TCCR0A = (1<<WGM01);
  TCCR0B = (1<<CS01);
  
  OCR0A = 1;
  
  TIMSK = (1<<OCIE0A)
  sei();
}

void ledBlink() {
  static unsigned int blinkTimer;
  
  if(microsecondsTimer >= 1000)
  {
      microsecondsTimer = 0;
      blinkTimer++;
  }
  
  if(blinkTimer >= 1000)
  {
      PORTB ^= (1<<PINB3);
      blinkTimer = 0;
  }
}

int main(void)
{ 
  // Set LED pin to OUTPUT mode
  DDRB |= (1 << PINB3);
  
  timer0_Init();

  while (1)
  {
      ledBlink();
  }
}

如果您使用的是內部時鍾,它可能會被 8 分頻。要禁用時鍾分頻,您必須在 4 個時鍾周期內禁用預分頻器(原子操作):

int main(void)
{ 
  // Reset clock prescaling
  CLKPR = (1<<CLKPR);
  CLKPR = 0x00;
  // ...

請嘗試此解決方案,如果它有效,請提供反饋。 也許你可以用示波器來驗證它......

請注意,在 8 位微控制器上處理 unsigned long 操作需要超過 1 個時鍾周期。 也許使用 unsigned int 或 unsigned char 會更好。 主循環也不應該包含很多 if 指令。 否則必須實現微秒定時器的糾錯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM