Pola terbaik untuk mikrokontroler WFI (tunggu-interupsi) pada Cortex (ARM)

18

Saya sedang mencari untuk mengembangkan perangkat lunak bertenaga baterai menggunakan pengontrol EFM Gekko (http://energymicro.com/) dan ingin pengontrol tertidur setiap kali tidak ada yang berguna untuk dilakukan. Instruksi WFI (Wait For Interrupt) digunakan untuk tujuan ini; itu akan membuat prosesor tidur sampai terjadi gangguan.

Jika tidur diaktifkan dengan menyimpan sesuatu di suatu tempat, seseorang dapat menggunakan operasi load-eksklusif / toko-eksklusif untuk melakukan sesuatu seperti:

  // dont_sleep akan dimuat dengan 2 setiap kali sesuatu terjadi itu
  // harus memaksa loop utama untuk berputar setidaknya sekali. Jika ada interupsi
  // Terjadi yang menyebabkannya diatur ulang ke 2 selama pernyataan berikut,
  // perilaku akan seolah-olah interupsi terjadi setelahnya.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  while (! dont_sleep)
  {
    // Jika interupsi terjadi antara pernyataan berikutnya dan store_exclusive, jangan tidur
    load_exclusive (SLEEP_TRIGGER);
    if (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Jika terjadi interupsi antara operasi load_exclusive dan store_exclusive, efeknya akan melewatkan store_exclusive, sehingga menyebabkan sistem untuk menjalankan loop sekali lagi (untuk melihat apakah interupsi telah mengatur dont_sleep). Sayangnya, Gekko menggunakan instruksi WFI daripada alamat tulis untuk memicu mode tidur; menulis kode suka

  if (! dont_sleep)
    WFI ();

akan menjalankan risiko bahwa interupsi dapat terjadi antara 'jika' dan 'wfi' dan mengatur dont_sleep, tetapi wfi akan tetap melanjutkan dan mengeksekusi. Apa pola terbaik untuk mencegah itu? Atur PRIMASK ke 1 untuk mencegah interupsi mengganggu prosesor sesaat sebelum menjalankan WFI, dan menghapusnya segera setelah itu? Atau ada trik yang lebih baik?

EDIT

Saya bertanya-tanya tentang bit Event. Secara umum, ini dimaksudkan untuk dukungan multi-prosesor, tetapi bertanya-tanya apakah sesuatu seperti yang berikut ini bisa berfungsi:

  if (dont_sleep)
    SEV (); / * Akan membuat mengikuti bendera acara WFE yang jelas tetapi tidak tidur * /
  WFE ();

Setiap interupsi yang diset jangan_sleep juga harus menjalankan instruksi SEV, jadi jika interupsi terjadi setelah tes "jika", WFE akan menghapus flag event tetapi tidak pergi tidur. Apakah itu terdengar seperti paradigma yang baik?

supercat
sumber
1
Instruksi WFI tidak membuat inti tertidur jika kondisi bangunnya benar ketika instruksi dijalankan. Sebagai contoh jika ada IRQ yang tidak jelas ketika WFI dijalankan itu bertindak sebagai NOP.
Tandai
@ Mark: Masalahnya adalah jika interupsi diambil antara "if (! Dont_sleep)" dan "WFI", kondisi interrupt tidak lagi tertunda ketika WFI dieksekusi, tetapi interupsi mungkin telah mengatur dont_sleep karena itu melakukan sesuatu yang akan membenarkan loop utama menjalankan iterasi lain. Pada aplikasi Cypress PSOC milik saya, setiap interupsi yang seharusnya menyebabkan wakeup yang diperpanjang akan membawa sial tumpukan jika kode jalur utama hendak tidur, tetapi itu tampaknya cukup menjijikkan dan saya mengerti ARM mencegah manipulasi tumpukan tersebut.
supercat
@supercat Gangguan ini mungkin atau mungkin tidak dihapus ketika WFI dieksekusi. Terserah Anda dan kapan / di mana Anda memilih untuk menghapus interupsi. Singkirkan variabel dont_sleep dan gunakan interupsi bertopeng untuk menandakan kapan Anda ingin tetap terjaga atau tidur. Anda bisa menghilangkan pernyataan if bersama-sama dan meninggalkan WFI di akhir loop utama. Jika Anda telah melayani semua permintaan, kosongkan IRQ agar Anda dapat tidur. Jika Anda perlu tetap terjaga, memicu IRQ, yang bertopeng sehingga tidak ada yang terjadi, tetapi ketika WFI mencoba untuk mengeksekusinya akan NOP.
Tandai
2
@supercat Pada tingkat yang lebih mendasar, tampaknya Anda mencoba mencampurkan desain interrupt-driven dengan desain 'big main', yang biasanya tidak kritis waktu, seringkali berbasis polling dan memiliki sedikit gangguan. Mencampur ini bisa menjadi agak jelek lebih cepat. Jika memungkinkan pilih satu paradigma desain atau yang lain untuk digunakan. Ingat bahwa dengan pengontrol interupsi modern, Anda pada dasarnya mendapatkan multitasking preemptive antara interupsi dan apa yang berarti antrian tugas (service one interrupt, lalu prioritas lebih tinggi berikutnya, dll). Gunakan itu untuk keuntungan Anda.
Tandai
@ Mark: Saya mengembangkan sistem yang menggunakan PIC 18x dengan cukup baik dalam aplikasi bertenaga baterai; karena keterbatasan tumpukan, ia tidak bisa menangani terlalu banyak dalam interupsi, sehingga sebagian besar barang ditangani dalam loop utama dengan dasar yang nyaman. Ini sebagian besar bekerja dengan cukup baik, meskipun ada beberapa tempat di mana hal-hal diblokir selama satu atau dua detik karena operasi yang berjalan lama. Jika saya bermigrasi ke ARM, saya dapat menggunakan RTOS sederhana untuk membuatnya lebih mudah untuk membagi operasi yang sudah berjalan lama, tapi saya tidak yakin apakah akan menggunakan multitasking preemptive atau kooperatif.
supercat

Jawaban:

3

Saya tidak sepenuhnya memahami dont_sleephal itu, tetapi satu hal yang dapat Anda coba adalah melakukan "pekerjaan utama" pada penangan PendSV, atur ke prioritas terendah. Kemudian, cukup jadwalkan PendSV dari penangan lain setiap kali Anda perlu melakukan sesuatu. Lihat di sini cara melakukannya (ini untuk M1 tetapi M3 tidak terlalu berbeda).

Hal lain yang bisa Anda gunakan (mungkin bersama dengan pendekatan sebelumnya) adalah fitur Sleep-on-exit. Jika Anda mengaktifkannya, prosesor akan tidur setelah keluar dari penangan ISR terakhir, tanpa Anda harus memanggil WFI. Lihat beberapa contoh di sini .

Igor Skochinsky
sumber
5
instruksi WFI tidak memerlukan interupsi untuk mengaktifkan prosesor, bit F dan I dalam CPSR diabaikan.
Tandai
1
@Mark, saya pasti melewatkan sedikit itu di dokumen, apakah Anda memiliki beberapa tautan / petunjuk tentang hal itu? Apa yang terjadi pada sinyal interupsi yang membangun inti? Apakah tetap tertunda hingga interupsi diaktifkan lagi?
Igor Skochinsky
Manual referensi ASM ada di sini: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/ ... informasi lebih spesifik untuk cortex-m3 ada di sini: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a / ... singkatnya ketika interupsi bertopeng menjadi tertunda inti bangun dan melanjutkan operasi setelah instruksi WFI. Jika Anda mencoba mengeluarkan WFI lain tanpa membersihkan interupsi yang tertunda, WFI akan bertindak sebagai NOP (inti tidak akan tidur karena kondisi bangun untuk WFI benar).
Tandai
@ Mark: Satu hal yang saya pertimbangkan adalah memiliki interrupt handler yang menyetel dont_sleep juga menjalankan instruksi SEV ("Set Event"), dan kemudian menggunakan WFE ("Tunggu Acara") daripada WFI. Contoh-contoh Gekko tampaknya menggunakan WFI, tapi saya pikir WFE mungkin juga berfungsi. Adakah pikiran?
supercat
10

Letakkan di bagian kritis. ISR tidak akan berjalan, jadi Anda tidak menjalankan risiko dont_sleep berubah sebelum WFI, tetapi mereka tetap membangunkan prosesor dan ISR akan dijalankan segera setelah bagian kritis berakhir.

uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
  WFI();
ExitCriticalSection(interruptStatus);

Lingkungan pengembangan Anda mungkin memiliki fungsi bagian kritis, tetapi kira-kira seperti ini:

EnterCriticalSection adalah:

MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */

ExitCriticalSection adalah:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */
Kenzi
sumber
2
Anehnya, sejumlah perpustakaan ARM menggunakan implementasi bagian kritis yang menggunakan penghitung global alih-alih mempertahankan status secara lokal. Saya menemukan bahwa pikiran membingungkan karena pendekatan penghitung lebih rumit dan hanya akan bekerja jika semua sistem kode menggunakan penghitung yang sama.
supercat
1
Tidak akan interupsi dinonaktifkan sampai kita keluar dari bagian kritis? Jika demikian, bukankah WFI akan menyebabkan CPU menunggu tanpa batas?
Corneliu Zuzu
1
@Kenzi Shrimp's menunjuk ke utas diskusi Linux yang menjawab pertanyaan saya sebelumnya. Saya mengedit jawaban dan jawaban Anda untuk menjelaskannya.
Corneliu Zuzu
@CorneliuZuzu Mengedit jawaban orang lain untuk memasukkan diskusi Anda sendiri bukanlah ide yang bagus. Menambahkan kutipan untuk meningkatkan jawaban 'tautan saja' adalah masalah yang berbeda. Jika Anda memiliki pertanyaan nyata tentang kemenangan Anda, mungkin tanyakan sebagai pertanyaan, dan tautkan ke pertanyaan ini.
Sean Houlihane
1
@SeanHoulihane Saya belum membatalkan atau menghapus apa pun dari jawabannya. Klarifikasi singkat tentang mengapa ini bekerja bukanlah diskusi terpisah. Sejujurnya saya tidak merasa jawaban ini pantas untuk dipilih tanpa klarifikasi WFI, tetapi itu yang paling layak saya terima.
Corneliu Zuzu
7

Ide Anda baik-baik saja, inilah yang diterapkan oleh Linux. Lihat di sini .

Kutipan yang berguna dari utas diskusi yang disebutkan di atas untuk mengklarifikasi mengapa WFI bekerja meskipun interupsi dinonaktifkan:

Jika Anda berniat diam sampai gangguan berikutnya, Anda harus melakukan beberapa persiapan. Selama persiapan itu, interupsi dapat menjadi aktif. Gangguan seperti itu mungkin merupakan peristiwa bangun yang Anda cari.

Tidak peduli sebagus apa pun kode Anda, jika Anda tidak menonaktifkan interupsi, Anda akan selalu berlomba antara bersiap untuk tidur dan benar-benar akan tidur, yang berakibat pada peristiwa bangun yang hilang.

Inilah sebabnya mengapa semua CPU ARM yang saya ketahui akan bangun meskipun mereka ditutupi pada CPU inti (CPSR I bit.)

Ada hal lain dan Anda harus lupa menggunakan mode siaga.

Udang
sumber
1
Anda mengacu pada menonaktifkan interupsi pada saat instruksi WFI atau WFE? Apakah Anda melihat perbedaan yang berarti antara menggunakan WFI atau WFE untuk tujuan tersebut?
supercat
1
@supercat: Saya pasti akan menggunakan WFI. WFE IMO terutama untuk petunjuk sinkronisasi antara core dalam sistem multicore (misalnya melakukan WFE pada kegagalan spinlock dan mengeluarkan SEV setelah keluar dari spinlock). Selain itu, WFE memperhitungkan flag masking interupsi sehingga tidak berguna di sini seperti WFI. Pola ini benar-benar berfungsi dengan baik di Linux.
Udang
2

Berasumsi bahwa:

  1. Utas utama menjalankan tugas latar belakang
  2. Interupsi hanya menjalankan tugas prioritas tinggi dan tidak ada tugas latar belakang
  3. Utas utama dapat diinterupsi kapan saja (biasanya topeng tidak akan terputus)

Maka solusinya adalah dengan menggunakan PRIMASK untuk memblokir interupsi antara validasi flag dan WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();
hdante
sumber
0

Bagaimana dengan mode Tidur Saat Keluar? Ini secara otomatis masuk ke mode tidur kapan pun pawang IRQ keluar, sehingga sebenarnya tidak ada "mode normal" yang berjalan setelah itu dikonfigurasi. IRQ terjadi, ia bangun dan menjalankan handler, dan kembali tidur. Tidak perlu WFI.

billt
sumber
2
Bagaimana seharusnya orang berurusan dengan fakta bahwa jenis tidur yang menjadi tempat prosesor jatuh mungkin bervariasi berdasarkan pada sesuatu yang terjadi selama interupsi? Sebagai contoh, acara pin-perubahan mungkin menunjukkan bahwa data serial mungkin akan datang, dan karena itu prosesor harus menjaga osilator jam utama tetap berjalan sambil menunggu data. Jika loop utama membersihkan flag event, memeriksa apa yang terjadi, dan menempatkan prosesor ke mode sleep yang sesuai dengan WFI, maka setiap interupsi yang mungkin memengaruhi mode apa yang sesuai akan mengatur flag event ...
supercat
... dan batalkan tidur. Memiliki satu pengendali loop utama kontrol mode tidur tampaknya lebih bersih daripada harus khawatir tentang hal itu di setiap interupsi. Harus "memutar" bagian dari loop utama pada setiap interupsi mungkin tidak efisien secara optimal, tetapi seharusnya tidak terlalu buruk, terutama jika semua interupsi yang mungkin mempengaruhi perilaku tidur mengenai beberapa bendera.
supercat