2009-05-21 41 views
4

這個問題是關於編程沒有OS的小型微控制器。特別是,我目前對PICs感興趣,但問題是一般的。使用定時器保持時間中斷嵌入式微控制器

我見過幾次的保持時間以下模式:

定時器中斷代碼(比如定時器觸發每秒):

... 
if (sec_counter > 0) 
    sec_counter--; 
... 

主線代碼(非中斷):

sec_counter = 500; // 500 seconds 

while (sec_counter) 
{ 
    // .. do stuff 
} 

主線代碼可能會重複,將計數器設置爲各種值(不僅僅是秒)等等。

在我看來,當主線代碼中的sec_counter分配不是原子時,這裏有一個競爭條件。例如,在PIC18中,分配被轉換爲4個ASM語句(在此之前加載每個字節並從存儲體中選擇正確的字節)。如果中斷代碼出現在中間,則最終值可能會損壞。

奇怪的是,如果賦值小於256,賦值原子,所以沒有問題。

我對這個問題正確嗎? 您使用什麼模式來正確實現此類行爲?我看到幾個選項:

  • 禁止中斷每個任務之前sec_counter並啓用後 - 這是不是漂亮
  • 不要使用中斷,但相應地開始,然後調查的一個單獨的計時器。這是乾淨的,但用完了一個定時器(在前面的情況下,1秒定時器也可用於其他目的)。

還有其他想法嗎?

回答

2

PIC體系結構儘可能原子化。它確保對存儲器文件的所有讀取 - 修改 - 寫入操作都是「原子」的。儘管執行整個讀取 - 修改 - 寫入需要4個時鐘,但是所有4個時鐘都在單個指令中消耗,並且下一個指令使用下一個4個時鐘週期。這是管道工作的方式。在8個時鐘中,兩條指令正在處理中。

如果該值大於8位,則它成爲一個問題,因爲PIC是一個8位機器,而較大的操作數在多個指令中處理。這將引入原子問題。

1

編寫該值,然後檢查它是否是所需的值似乎是最簡單的選擇。

do { 
sec_counter = value; 
} while (sec_counter != value); 

順便說一句,你應該使用是否C.

如果你需要閱讀的價值,那麼你可以讀了兩遍使變量波動。

do { 
    value = sec_counter; 
} while (value != sec_counter); 
+0

有趣的方法,雖然懷疑比禁用中斷更清潔。我不知道我需要volatile,因爲無論如何編譯器優化都是禁用的。 – 2009-05-21 13:08:05

+0

我希望這己技巧被註釋掉,當你使用它,因爲它要氣色好怪異的人誰不立即意識到它的防護安劍錚,卓傑中斷改變一個「C指令」中的值。 – Martin 2009-05-22 06:50:03

+0

從多個內存映射寄存器中讀取禁用中斷不起作用時,它也可以工作。 – Dipstick 2009-05-22 15:53:41

0

那麼,比較彙編代碼是什麼樣的?

考慮到它倒數,並且它只是一個零比較,它應該是安全的,如果它首先檢查MSB,然後檢查LSB。可能存在破壞,但它是否在0x100和0xff之間的中間並且損壞的比較值是0x1ff並不重要。

+0

這個問題與我的想法不符。這是主線代碼和ISR寫入相同的變量 – 2009-05-21 13:10:44

1

在設置計數器之前,您一定需要禁用中斷。醜陋,因爲它可能是必要的。在配置影響ISR方法的硬件寄存器或軟件變量之前,始終禁用中斷是一種很好的做法。如果您使用C編寫,則應將所有操作視爲非原子操作。如果你發現你必須多次查看生成的程序集,那麼最好放棄C並在程序集中進行編程。根據我的經驗,這種情況很少。

關於討論過這個問題,這是我的建議:

ISR: 
if (countDownFlag) 
{ 
    sec_counter--; 
} 

,並設置計數器:

// make sure the countdown isn't running 
sec_counter = 500; 
countDownFlag = true; 

... 
// Countdown finished 
countDownFlag = false; 

你需要一個額外的變量,是更好的功能來包裝的一切:

void startCountDown(int startValue) 
{ 
    sec_counter = 500; 
    countDownFlag = true; 
} 

這樣你就可以抽象出起始方法(如果需要的話可以隱藏起來很醜)。例如,您可以輕鬆將其更改爲啓動硬件計時器,而不會影響方法的調用者。

0

你現在使用你的計時器的方式,它不會計算整秒,因爲你可能會在一個週期中改變它。 所以,如果你不關心它。在我看來,最好的方法是閱讀價值,然後比較差異。 (因爲定時器有優先權)

如果你對時間值更嚴格,我會自動禁用定時器,一旦它減少到0,並清除計時器的內部計數器並在需要時激活。

0

將main()中的代碼部分移動到適當的函數中,並讓ISR有條件地調用它。另外,爲了避免任何類型的延遲或丟失滴答,選擇此定時器ISR爲高級中斷(PIC18有兩個級別)。

1

由於對sec_counter變量的訪問不是原子的,如果您需要確定性行爲,在訪問主線代碼中的此變量並在訪問後恢復中斷狀態之前,確實無法避免禁用中斷。這可能是一個比爲這個任務專門配置硬件定時器更好的選擇(除非你有多餘的定時器,在這種情況下你可以使用它)。

1

如果您下載Microchip免費的TCP/IP協議棧,那裏有一些例程使用定時器溢出來跟蹤經過的時間。特別是「tick.c」和「tick.h」。只需將這些文件複製到您的項目。

在這些文件中你可以看到他們是如何做到的。

1

對於小於256個原子的移動並不是很好奇 - 移動一個8位的值是一個操作碼,這樣就像原子一樣。

PIC等微控制器的最佳解決方案是在更改定時器值之前禁用中斷。當你在主循環中改變變量並且如果你想要的話,你可以檢查中斷標誌的值。使它成爲一個可以改變變量值的函數,你甚至可以從ISR調用它。

0

一種方法是有一箇中斷保持一個字節變量,還有別的都會調用至少每256次反被擊中;這樣做:

 
// ub==unsigned char; ui==unsigned int; ul==unsigned long 
ub now_ctr; // This one is hit by the interrupt 
ub prev_ctr; 
ul big_ctr; 

void poll_counter(void) 
{ 
    ub delta_ctr; 

    delta_ctr = (ub)(now_ctr-prev_ctr); 
    big_ctr += delta_ctr; 
    prev_ctr += delta_ctr; 
}

細微變化,如果你不介意迫使中斷的計數器保持同步與你的大計數器的LSB:

 
ul big_ctr; 
void poll_counter(void) 
{ 
    big_ctr += (ub)(now_ctr - big_ctr); 
} 
0

沒有一個解決的問題閱讀多字節硬件寄存器(例如定時器。 定時器可以翻轉,增加它的第二個字節,而你讀它。

說這是0x0001ffff和你讀它,你可能摹等於0x0010ffff或0x00010000。

16位外圍寄存器是揮發性至代碼。

對於任何揮發性「變量」,我用的是雙重閱讀技巧。

do { 
     t = timer; 
} while (t != timer);