Gömülü sistemlerde kesinti kullanırken global değişkenlerden kaçınma


13

Küresel değişkenlerden kaçınan gömülü bir sistem için bir ISR ile programın geri kalanı arasında iletişim kurmanın iyi bir yolu var mı?

Genel örüntü, ISR ve programın geri kalanı arasında paylaşılan ve bir bayrak olarak kullanılan küresel bir değişkene sahip olmak gibi görünüyor, ancak küresel değişkenlerin bu kullanımı bana tahılda gidiyor. Ben avr-libc tarzı ISR kullanarak basit bir örnek dahil ettik:

volatile uint8_t flag;

int main() {
    ...

    if (flag == 1) {
        ...
    }
    ...
}

ISR(...) {
    ...
    flag = 1;
    ...
}

Temelde kapsam belirleme sorununun ne olduğunu göremiyorum; ISR ve programın geri kalanı tarafından erişilebilen herhangi bir değişken doğal olarak küresel olmalı mı? Buna rağmen, insanların genellikle "küresel değişkenler ISR'ler ve programın geri kalanı arasındaki iletişimi uygulamanın bir yolu " (vurgu mayın) gibi şeyleri söylediğini gördüm . başka yöntemler varsa, bunlar nedir?



1
Programın geri kalanının TÜMÜNÜ erişebileceği doğru değildir; değişkeni statik olarak bildirdiyseniz, yalnızca değişkenin bildirildiği dosya onu görür. Bir dosyanın tamamı içinde görünür olan değişkenlere sahip olmak hiç de zor değil, programın geri kalanı değil ve yardımcı olabilir.
DiBosco

1
yanında bayrak normal program akışı dışında kullandığınız / değiştirdiğiniz için geçici olarak bildirilmelidir. Bu, derleyiciyi herhangi bir okuma / yazma işaretini optimize etmemeye ve gerçek okuma / yazma işlemini gerçekleştirmeye zorlar.
sonraki

@ next-hack Evet, bu kesinlikle doğru, üzgünüm kısa sürede bir örnek bulmaya çalışıyordum.

Yanıtlar:


18

Bunu yapmanın fiili standart bir yolu vardır (C programlamayı varsayarak):

  • Kesmeler / ISR'ler düşük seviyelidir ve bu nedenle yalnızca kesmeyi oluşturan donanımla ilgili sürücü içinde uygulanmalıdır. Başka bir yere değil, o sürücünün içine yerleştirilmelidirler.
  • ISR ile tüm iletişim sadece sürücü ve sürücü tarafından yapılır. Programın diğer bölümlerinin bu bilgilere erişmesi gerekiyorsa, ayarlayıcı / alıcı işlevleri veya benzerleri yoluyla sürücüden talep etmesi gerekir.
  • "Global" değişkenleri bildirmemelisiniz. Dış bağlantıya sahip global anlam dosyası kapsamı değişkenleri. Yani: externanahtar kelime ile ya da sadece yanlışlıkla çağrılabilen değişkenler .
  • Bunun yerine, sürücü içinde özel kapsüllemeyi zorlamak için, sürücü ile ISR arasında paylaşılan tüm bu değişkenler bildirilecektir static. Böyle bir değişken global değildir ancak bildirildiği dosya ile sınırlıdır.
  • Derleyici optimizasyonu sorunlarını önlemek için, bu tür değişkenler de olarak bildirilmelidir volatile. Not: Bu atomik erişim sağlamaz veya yeniden girişi çözmez!
  • ISR'nin değişkene yazması durumunda, sürücüde genellikle bir tür yeniden giriş mekanizması gereklidir. Örnekler: kesme devre dışı bırakma, global kesme maskesi, semafor / muteks veya garantili atom okumaları.

Not: ISR işlev prototipini başka bir dosyada bulunan bir vektör tablosuna yerleştirmek için bir başlık aracılığıyla göstermeniz gerekebilir. Ancak bir kesinti olduğunu ve program tarafından çağrılmaması gerektiğini belgelediğiniz sürece bu bir sorun değildir.
Lundin

Karşı argüman ayarlayıcı / alma işlevlerini kullanmanın artan yükü (ve ekstra kodu) olsaydı ne söylerdiniz? 8 bit gömülü cihazlarımız için kod standartlarını düşünerek bu konuyu kendim hallediyorum.
Leroy105

2
@ Leroy105 C dili, sonsuza dek satır içi işlevleri destekledi. Her ne kadar kullanımı bile kullanılmıyor olsa da, inlinederleyiciler kod optimizasyonunda daha akıllı ve daha akıllı hale geliyor. Genel gider konusunda endişelenmenin "önceden olgunlaşmış optimizasyon" olduğunu söyleyebilirim - çoğu durumda genel gider önemli değildir, makine kodunda bile varsa.
Lundin

2
Bununla birlikte, ISR sürücülerinin yazılması durumunda, tüm programcıların% 80-90'ı (burada abartmıyor) her zaman içlerinde bir şeyler yanlış anlar. Sonuç ince hatalardır: yanlış temizlenmiş bayraklar, eksik uçucudan yanlış derleyici optimizasyonu, yarış koşulları, berbat gerçek zamanlı performans, yığın taşmaları vb. daha da arttı. Ayarlayıcı / alıcıların küçük bir ek yük getirmesi gibi, çevresel ilgi alanları hakkında endişelenmeden önce bir hata ücretsiz sürücü yazmaya odaklanın.
Lundin

10
küresel değişkenlerin bu kullanımı bana taneye karşı gidiyor

Asıl sorun bu. AŞ bunu.

Şimdi diz sarsıntıları bunun nasıl kirli olduğunu hemen söylemeden önce, bunu biraz nitelendireyim. Küresel değişkenleri aşmada kullanmak kesinlikle tehlikelidir. Ancak, bazen küçük kaynak sınırlı sistemlerde önemli olan verimliliği artırabilirler.

Anahtar, bunları ne zaman makul bir şekilde kullanabileceğinizi düşünmek ve sadece başlamayı bekleyen bir hataya karşı kendinizi belaya sokma ihtimaliniz yoktur. Her zaman ödünleşmeler vardır. İken genellikle kesme ve ön plan kodu arasındaki iletişim için küresel değişkenleri kaçınarak bir dinler aşırı, diğer çoğu kılavuzları gibi, alarak, bir undertandable kılavuzdur karşı-üretken.

Kesme ve ön plan kodu arasında bilgi aktarmak için bazen global değişkenleri kullandığım bazı örnekler:

  1. Sistem saat kesmesi tarafından yönetilen saat onay sayaçları. Genellikle her 1 ms'de bir periyodik saat kesmesi var. Bu genellikle sistemdeki çeşitli zamanlama için kullanışlıdır. Bu bilgileri sistemin geri kalanının kullanabileceği kesme rutininden çıkarmanın bir yolu, küresel bir saat işareti sayacı tutmaktır. Kesme rutini sayacı her saat işaretinde arttırır. Ön plan kodu sayacı her zaman okuyabilir. Genellikle bunu 10 ms, 100 ms ve hatta 1 saniye keneler için yaparım.

    1 ms, 10 ms ve 100 ms kenelerin tek bir atomik işlemle okunabilen bir kelime boyutunda olduğundan emin olun. Üst düzey bir dil kullanıyorsanız, derleyiciye bu değişkenlerin eşzamansız olarak değişebileceğini söyleyin. C'de, örneğin, bunların dışa uçucu olduğunu beyan edersiniz . Tabii ki bu bir konserve içerme dosyasına giren bir şey, bu yüzden her proje için bunu hatırlamanıza gerek yok.

    Bazen 1 s kene sayacını toplam geçen zaman sayacı yapıyorum, bu yüzden 32 bit genişliğinde olun. Bu, kullandığım küçük mikro mikropların çoğunda tek bir atomik işlemle okunamıyor, bu yüzden küresel değil. Bunun yerine, çok kelimeli değeri okuyan, okumalar arasındaki olası güncellemeleri ele alan ve sonucu döndüren bir rutin sağlanır.

    Tabii daha küçük 1 ms, 10 ms, vb kene sayaçları almak için rutin olabilirdi . Ancak, bu sizin için gerçekten çok az şey yapar, tek bir kelimeyi okumak yerine birçok talimat ekler ve başka bir çağrı yığını konumu kullanır.

    Dezavantajı nedir? Birisinin yanlışlıkla sayaçlardan birine yazan bir yazım hatası yapabileceğini düşünüyorum, bu da sistemdeki diğer zamanlamayı bozabilir. Bir sayaca kasten yazmak mantıklı olmaz, bu nedenle bu tür bir hatanın yazım hatası gibi kasıtsız bir şey olması gerekir. Çok muhtemel görünmüyor. Bunu hatırlamıyorum hiç de 100 küçük mikrodenetleyici projelerinde oluyor.

  2. Son filtrelenmiş ve ayarlanmış A / D değerleri. Ortak bir şey, bir A / D'den bir rutin tutamaç okuması yapmaktır. Genellikle analog değerleri gerekenden daha hızlı okurum, sonra biraz düşük geçişli filtreleme uygularım. Genellikle uygulanan ölçeklendirme ve ofset de vardır.

    Örneğin, A / D 24 V beslemesini ölçmek için bir voltaj bölücünün 0 ila 3 V çıkışını okuyor olabilir. Birçok okuma, bir miktar filtreleme yoluyla gerçekleştirilir, daha sonra ölçeklendirilir, böylece nihai değer milivolt cinsindendir. Besleme 24.015 V ise, son değer 24015'dir.

    Sistemin geri kalanı sadece besleme voltajını gösteren canlı güncellenmiş bir değer görür. Tam olarak ne zaman güncellendiğini bilmiyor ya da umursamıyor, özellikle de düşük geçiş filtresi çökme süresinden çok daha sık güncellendiğinden.

    Yine, bir arayüz rutin olabilir kullanılan, ancak bundan çok az fayda elde edilebilir. Güç değişkenine ihtiyacınız olduğunda sadece global değişkeni kullanmak çok daha basittir. Sadeliğin sadece makine için değil, aynı zamanda daha az insan hatası şansı anlamına geldiğini unutmayın.


Yavaş bir haftada, gerçekten kodumu nitpick yapmaya çalışırken terapiye gidiyorum. Lundin'in değişken erişimi kısıtlama konusundaki görüşünü görüyorum, ancak gerçek sistemlerime bakıyorum ve bunun çok uzak bir olasılık olduğunu düşünüyorum. HERHANGİ BİR KİŞİ gerçekten sistem açısından kritik bir küresel değişkeni sarsacaktı. Getter / Setter işlevleri, genel olarak maliyete karşı genel bir maliyete neden olur ve bunları kabul etmek oldukça basit programlardır ...
Leroy105

3
@ Leroy105 Sorun küresel değişkeni kasten kötüye kullanan "teröristler" değil. Ad alanı kirliliği daha büyük projelerde bir sorun olabilir, ancak bu iyi adlandırma ile çözülebilir. Hayır, asıl sorun programcı küresel değişkeni istendiği gibi kullanmaya çalışıyor, ancak bunu doğru şekilde yapamıyor. Ya tüm ISR'lerde var olan yarış koşulu sorununun farkında olmadıkları için ya da zorunlu koruma mekanizmasının uygulanmasını bozdukları için ya da basitçe genel değişkenin kodun her tarafına yayıldıkları için, sıkı bağlantı ve okunamayan kod.
Lundin

Puanlarınız Olin geçerlidir, ancak bile bu örneklerde, değiştirilmesi extern int ticks10msile inline int getTicks10ms()diğer taraftan yanlışlıkla programın diğer bölümlerinde değerini değiştirmek zor hale getirecek ise, montaj derlenmiş kesinlikle hiçbir fark yaratmak ve ayrıca ekleyebileceksiniz bu çağrıyı "bağlamanın" bir yolu (örneğin, birim testi sırasındaki zamanı alay etmek, bu değişkene erişimi kaydetmek ya da her neyse). San bir programcının bu değişkeni sıfıra değiştirme olasılığını tartışsanız bile, bir satır içi alıcısının maliyeti yoktur.
Groo

@ Graro: Bu yalnızca satır içi işlevleri destekleyen bir dil kullanıyorsanız doğrudur ve alıcı işlevinin tanımının herkes tarafından görülebilir olması gerektiği anlamına gelir. Aslında üst düzey bir dil kullanırken getter fonksiyonlarını daha çok, global değişkenleri daha az kullanıyorum. Montajda, küresel bir değişkenin değerini almak, alıcı işlevi ile uğraşmaktan çok daha kolaydır.
Olin Lathrop

Tabii ki, satır içi yapamıyorsanız, seçim o kadar basit değildir. Satır içi işlevlerle (ve zaten önceden satır içi uzantıları destekleyen birçok C99 derleyicisinin) performansın alıcılara karşı bir argüman olamayacağını söylemek istedim. Makul bir optimize edici derleyici ile, aynı üretilen montaj ile sonuçlanmalıdır.
Groo

2

Herhangi bir kesinti global bir kaynak olacaktır. Bununla birlikte, bazen, aynı kodu paylaşan birkaç kesmenin olması yararlı olabilir. Örneğin, bir sistemin, hepsi benzer gönderme / alma mantığı kullanması gereken birkaç UART'a sahip olabilir.

Kesme işlemcisi veya işaretçiler tarafından kullanılan şeyleri bir yapı nesnesine yerleştirmek ve daha sonra gerçek donanım kesme işleyicilerinin aşağıdaki gibi bir şey olmasını sağlamak için güzel bir yaklaşım:

void UART1_handler(void) { uart_handler(&uart1_info); }
void UART2_handler(void) { uart_handler(&uart2_info); }
void UART3_handler(void) { uart_handler(&uart3_info); }

Nesneler uart1_info, uart2_infovb. Global değişkenler olacaktır, ancak kesme işleyicileri tarafından kullanılan tek global değişkenler olacaktır . İşleyicilerin dokunacağı diğer her şey bunların içinde ele alınacaktır.

Hem kesme işleyici hem de ana hat kodu tarafından erişilen her şeyin kalifiye olması gerektiğini unutmayın volatile. volatileKesinti işleyici tarafından kullanılacak her şey olarak ilan etmek en basit olabilir , ancak performans önemliyse, bilgiyi geçici değerlere kopyalayan, üzerlerinde çalışan ve daha sonra yeniden yazan bir kod yazmak isteyebilirsiniz. Örneğin, yazmak yerine:

if (foo->timer)
  foo->timer--;

yazmak:

uint32_t was_timer;
was_timer = foo->timer;
if (was_timer)
{
  was_timer--;
  foo->timer = was_timer;
}

İlk yaklaşımın okunması ve anlaşılması daha kolay olabilir, ancak ikincisinden daha az verimli olacaktır. Bunun bir endişe olup olmadığı uygulamaya bağlıdır.


0

İşte üç fikir:

Kapsamı tek bir dosyayla sınırlamak için flag değişkenini statik olarak bildirin.

Bayrak değişkenini özel yapın ve bayrak değerine erişmek için alıcı ve ayarlayıcı işlevlerini kullanın.

Bayrak değişkeni yerine semafor gibi bir sinyalleme nesnesi kullanın. ISR semaforu ayarlayacak / yayınlayacaktır.


0

Bir kesinti (yani, işleyicinize işaret eden vektör) global bir kaynaktır. Yığında veya yığın üzerinde bazı değişkenler kullansanız bile:

volatile bool *flag;  // must be initialized before the interrupt is enabled

ISR(...) {
    *flag = true;
}

veya 'sanal' işlevli nesne yönelimli kod:

HandlerObject *obj;

ISR(...) {
    obj->handler_function(obj);
}

… İlk adım, diğer verilere ulaşmak için gerçek bir global (veya en azından statik) değişken içermelidir.

Tüm bu mekanizmalar bir dolaylılık ekler, bu nedenle kesme döngüsünün son döngüsünü sıkmak istiyorsanız bu genellikle yapılmaz.


bayrağını geçici int * olarak bildirmelisiniz.
sonraki

0

Şu anda Cortex M0 / M4 için kodlama yapıyorum ve C ++'da kullandığımız yaklaşım (C ++ etiketi yok, bu yüzden bu cevap konu dışı olabilir) aşağıdaki gibidir:

CInterruptVectorTableDenetleyicinin gerçek kesme vektöründe saklanan tüm kesme hizmeti rutinlerini içeren bir sınıf kullanırız :

#pragma location = ".intvec"
extern "C" const intvec_elem __vector_table[] =
{
  { .__ptr = __sfe( "CSTACK" ) },           // 0x00
  __iar_program_start,                      // 0x04

  CInterruptVectorTable::IsrNMI,            // 0x08
  CInterruptVectorTable::IsrHardFault,      // 0x0C
  //[...]
}

Sınıf CInterruptVectorTable, kesme vektörlerinin bir soyutlamasını uygular, böylece çalışma sırasında kesme vektörlerine farklı işlevler bağlayabilirsiniz.

Bu sınıfın arayüzü şöyle görünür:

class CInterruptVectorTable  {
public :
    typedef void (*IsrCallbackfunction_t)(void);                      

    enum InterruptId_t {
        INTERRUPT_ID_NMI,
        INTERRUPT_ID_HARDFAULT,
        //[...]
    };

    typedef struct InterruptVectorTable_t {
        IsrCallbackfunction_t IsrNMI;
        IsrCallbackfunction_t IsrHardFault;
        //[...]
    } InterruptVectorTable_t;

    typedef InterruptVectorTable_t* PinterruptVectorTable_t;


public :
    CInterruptVectorTable(void);
    void SetIsrCallbackfunction(const InterruptId_t& interruptID, const IsrCallbackfunction_t& isrCallbackFunction);

private :

    static void IsrStandard(void);

public :
    static void IsrNMI(void);
    static void IsrHardFault(void);
    //[...]

private :

    volatile InterruptVectorTable_t virtualVectorTable;
    static volatile CInterruptVectorTable* pThis;
};

Vektör tablosunda saklanan işlevleri yapmanız gerekir, staticçünkü thisvektör tablosu bir nesne olmadığından denetleyici bir- işareti sağlayamaz . Bu sorunun pThisüstesinden gelmek için, içinde statik- işaretçi var CInterruptVectorTable. Statik kesme fonksiyonlarından birine girildiğinde pThis, bir nesnenin üyelerine erişmek için -pointer öğesine erişebilir CInterruptVectorTable.


Şimdi programda, bir kesme meydana geldiğinde çağrılacak SetIsrCallbackfunctionbir staticişleve bir işlev işaretçisi sağlamak için kullanabilirsiniz . İşaretçiler InterruptVectorTable_t virtualVectorTable.

Ve bir kesme fonksiyonunun uygulanması şöyle görünür:

void CInterruptVectorTable::IsrNMI(void) {
    pThis->virtualVectorTable.IsrNMI(); 
}

Böylece bu static, başka bir sınıfın (bu olabilir private) bir yöntemini çağırır ve daha sonra static thiso nesnenin üye değişkenlerine (yalnızca bir) erişmek için başka bir- işaretçisi içerebilir .

Sanırım IInterruptHandlernesneler için işaretçiler oluşturup saklayabilirsiniz, böylece static thistüm bu sınıflarda -pointer'a ihtiyacınız yoktur . (belki bunu mimarimizin bir sonraki tekrarında deneriz)

Diğer yaklaşım bizim için iyi static thissonuç verir , çünkü bir kesme işleyicisi uygulamak için izin verilen tek nesneler donanım soyutlama katmanının içindeki nesnelerdir ve genellikle her donanım bloğu için sadece bir nesnemiz vardır , bu yüzden -pointers ile iyi çalışır. Ve donanım soyutlama katmanı, daha ICallbacksonra donanımın üstündeki cihaz katmanında uygulanan denilen kesintilere başka bir soyutlama sağlar .


Global verilere erişiyor musunuz? Tabii ki yaparsınız, ancak gerekli global verilerin thisçoğunu -pointler ve kesme işlevleri gibi özel yapabilirsiniz.

Kurşun geçirmez değildir ve ek yük getirir. Bu yaklaşımı kullanarak bir IO-Link yığını uygulamak için mücadele edeceksiniz. Ancak zamanlamalar konusunda son derece sıkı değilseniz, her yerden erişilebilen global değişkenler kullanmadan modüllerde esnek bir kesinti ve iletişim soyutlaması elde etmek için oldukça iyi çalışır.


1
"böylece çalışma sırasında kesinti vektörlerine farklı işlevler bağlayabilirsiniz" Bu kötü bir fikir gibi geliyor. Programın "siklomatik karmaşıklığı" sadece çatıdan geçecekti. Tüm kullanım-vaka kombinasyonları, ne zamanlama ne de yığın kullanım çakışmaları olmayacak şekilde test edilmelidir. Çok sınırlı kullanışlılık IMO özellikli bir sürü kafa ağrısı. (Bir bootloader kasanız yoksa, bu başka bir hikaye) Genel olarak bu meta programlama kokuyor.
Lundin

@Lundin Ne demek istediğini anlamıyorum. DMA SPI için kullanılıyorsa, örneğin DMA kesmesini SPI kesme işleyicisine ve UART için kullanılıyorsa UART kesme işleyicisine bağlamak için kullanırız. Her iki işleyicinin de test edilmesi gerekir, ama sorun değil. Ve kesinlikle meta programlama ile ilgisi yok.
Arsenal

DMA bir şeydir, kesinti vektörlerinin çalışma zamanı ataması tamamen başka bir şeydir. Bir DMA sürücü kurulumunun çalışma zamanında değişken olmasına izin vermek mantıklıdır. Bir vektör tablosu, çok fazla değil.
Lundin

@Lundin Sanırım bununla ilgili farklı görüşlerimiz var, bunun hakkında bir sohbet başlatabiliriz, çünkü hala onunla olan probleminizi göremiyorum - bu yüzden cevabım çok kötü yazılmış olabilir, tüm kavram yanlış anlaşılmış olabilir.
Arsenal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.