Ichki tizimlardagi interruptlarni ishlatganda global o'zgaruvchining oldini olish

ISR va dasturning qolgan qismi o'rtasida global o'zgaruvchilardan xalos bo'lgan ko'milgan tizim uchun aloqa o'rnatishning yaxshi usuli bormi?

Umumiy naqsh ISR bilan dasturning qolgan qismi o'rtasida birlashtirilgan va bayroq sifatida ishlatiladigan global o'zgaruvchiga ega bo'lishi mumkin, ammo global o'zgaruvchanlikni qo'llash men uchun donga to'g'ri keladi. Avr-libc uslubidagi ISR-lardan foydalanib oddiy misol keltirdim:

volatile uint8_t flag;

int main() {
    ...

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

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

Aslini oladigan bo'lsak, muammolarni atrofida ko'rmayapman; ISR va dasturning qolgan qismlari tomonidan taqdim etiladigan har qanday o'zgaruvchiga, albatta, global bo'lishi kerak. Shunga qaramasdan, ko'pincha odamlar "ISRlar bilan dasturning qolgan qismi o'rtasidagi muloqotni amalga oshirishda global o'zgaruvchilar bir xil usulda" degan narsalar haqida gapirganini ko'rganman. boshqa usullar mavjud; boshqa usullar mavjud bo'lsa, ular nima?

9
qo'shib qo'ydi muallif Nick Alexeev, manba
Bundan tashqari, bayroq uchuvchi deb e'lon qilinishi kerak, chunki siz uni normal dastur oqimidan tashqari o'zgartirasiz/o'zgartirasiz. Bu esa, kompilyatorga har qanday o'qish/yozishni optimallashtirmaslik va haqiqiy o'qish/yozish jarayonini bajarish uchun majburiy bo'ladi.
qo'shib qo'ydi muallif agfa555, manba
Dasturning qolgan qismiga kirish imkoni bo'lishi shart emas; agar siz o'zgaruvchini statik deb e'lon qilsangiz, faqat o'zgaruvchining e'lon qilingan fayllari buni ko'radi. Dasturning qolgan qismini emas, balki yordam beradigan barcha fayllar ichida ko'rinadigan o'zgaruvchiga ega bo'lish qiyin emas.
qo'shib qo'ydi muallif Silicomancer, manba
@ next-hack Ha, bu albatta to'g'ri, afsuski, men tezda misol keltirmoqchiman.
qo'shib qo'ydi muallif user109324, manba

6 javoblar

Buni amalga oshirish uchun de-fakto standart yo'l mavjud (C dasturlashni nazarda tutgan holda):

  • Interrupts/ISRs past darajadadir va shuning uchun faqat haydovchiga interruptni ishlab chiqaruvchi apparat bilan bog'liq holda amalga oshirilishi kerak.
  • ISR bilan barcha aloqa faqat haydovchi va haydovchi tomonidan amalga oshiriladi. Agar dasturning boshqa qismlari ushbu ma'lumotlarga kirishga muhtoj bo'lsa, uni haydovchidan setter/getter funktsiyalari yoki shunga o'xshash holatlarda so'rashi kerak.
  • "Global" o'zgaruvchilarini e'lon qilishingiz kerak emas. Tashqi aloqada bo'lgan global ma'no faylining o'zgaruvchilari. Ya'ni: extern kalit so'zi bilan chaqirilishi mumkin bo'lgan o'zgaruvchan yoki oddiygina xato.
  • Buning o'rniga, haydovchi ichidagi shaxsiy inkassulyatsiyani majburlash uchun haydovchi bilan ISR o'rtasida almashiladigan barcha o'zgaruvchilar static deb e'lon qilinadi. Bunday o'zgaruvchi emas , lekin e'lon qilingan faylga cheklangan.
  • Derleyici optimallashtirish masalalarini oldini olish uchun, bunday variabellar uchuvchi deb ham e'lon qilinishi kerak. Eslatma: bu atomga kirishga ruxsat bermaydi yoki qayta kirib bo'lmaydi!
  • ISR o'zgaruvchiga yozganda, haydovchiga qayta kirish mexanizmining ba'zi shakllari talab qilinadi. Misollar: interrupt o'chirib qo'yish, global interrupt maskasi, semafor/mutex yoki kafolatlangan atom o'qishlari.
12
qo'shib qo'ydi
Eslatma: ISR funktsiyasi prototipini boshqa faylda joylashgan vektorli jadvalga joylashtirish uchun siz nom orqali yuborishingiz mumkin. Ammo, bu sizning chiqishingiz va dastur tomonidan chaqirilmasligi kerakligi haqida hujjat topshirgan ekan, bu sizning muammoingiz emas.
qo'shib qo'ydi muallif Neil Foley, manba
@ Leroy105 C tili hozirgacha abadiylik uchun ichki funksiyalarni qo'llab-quvvatladi. inline dan foydalanish hatto eski holga keladi, chunki kompilyatorlar kodni optimallashtirishda aqlli va aqlli bo'ladilar. Aytmoqchimanki, yuk tashish borasida tashvishlanish "oldindan etuk optimallash" dir - ko'p holatlarda, agar bu mashina kodi mavjud bo'lsa ham, ustun emas.
qo'shib qo'ydi muallif Neil Foley, manba
Aytish kerakki, ISR haydovchilarini yozishda, barcha dasturchilarning 80-90 foizi (bu erda shubhalanmaslik kerak) har doim noto'g'ri narsalar oladilar. Natijada nozik xatolar: noto'g'ri tozalagan bayroqlar, noto'g'ri kompilyator optimallashtirishi, uchuvchan bo'lmagan uchqunlar, poyga sharoitlari, porloq real vaqtda ishlash, to'plamlar va boshqalar. ISR ning haydovchiga to'g'ri qo'shilmasligi mumkin bo'lsa, bunday nozik xatolar ehtimolligi yanada ortdi. Atrof-muhitga oid narsalar haqida tashvishlanmasdan oldin, masalan, noqonuniy ravishda bepul haydovchini yozishga e'tibor bering, masalan, agar ajnabiy yoki grajdanlar bir oz yuk tashlasalar.
qo'shib qo'ydi muallif Neil Foley, manba
Aytganday, agar teskari argument belgilash/olish funktsiyalaridan foydalanishning oshishi (va qo'shimcha kod) bo'lsa, nima deb javob berardingiz? Men 8 gigabayt ichki qurilmam uchun kod standartlari haqida o'ylayman.
qo'shib qo'ydi muallif Leroy105, manba
global o'zgaruvchilardan foydalanish men uchun donga qarshi bo'ladi

Bu haqiqiy muammo. Qabul qiling.

Endi esa tiz cho'kib ketishdan oldin, bu nopokning qanday bo'lishi haqida o'ylashdan oldin, men buni biroz xursand qilaylik. Umuman olganda, global o'zgaruvchilardan ortiqcha foydalanish xavfi mavjud. Ammo, ular samaradorlikni oshirishi mumkin, bu esa ba'zida kichik resurslar bilan cheklangan tizimlarda muhim ahamiyatga ega.

Kalitni ulardan foydalanishingiz mumkin bo'lgan vaqt haqida o'ylash va o'zingizni muammolarni boshdan kechirishni kutayotgan xatolarga qaramaslik mumkin emas. Doimo kelishmovchiliklar bor. Interrupt va oldindan belgilangan kodlar orasidagi aloqa uchun global o'zgaruvchilardan qochib, ko'pincha boshqa diniy ko'rsatmalar singari uni dinlarga nisbatan juda samarali deb hisoblash juda muhimdir.

Interrupt va old fon kodi orasidagi ma'lumotlarni uzatish uchun ba'zida global o'zgaruvchilardan foydalanadigan ayrim misollar:

  1. Clock tick counters managed by the system clock interrupt. I usually have a periodic clock interrupt that runs every 1 ms. That is often useful for various timing in the system. One way to get this information out of the interrupt routine to where the rest of the system can use it is to keep a global clock tick counter. The interrupt routine increments the counter every clock tick. Foreground code can read the counter at any time. Often I do this for 10 ms, 100 ms, and even 1 second ticks.

    I make sure the 1 ms, 10 ms, and 100 ms ticks are of a word size that can be read in a single atomic operation. If using a high level language, make sure to tell the compiler that these variables can change asynchronously. In C, you declare them extern volatile, for example. Of course this is something that goes into a canned include file, so you don't need to remember that for every project.

    I sometimes make the 1 s tick counter the total elapsed up time counter, so make that 32 bits wide. That can't be read in a single atomic operation on many of the small micro I use, so that isn't made global. Instead, a routine is provided that reads the multi-word value, deals with possible updates between reads, and returns the result.

    Of course there could have been routines to get the smaller 1 ms, 10 ms, etc, tick counters too. However, that really does very little for you, adds a lot of instructions in place of reading a single word, and uses up another call stack location.

    What's the downside? I suppose someone could make a typo that accidentally writes to one of the counters, which then could mess up other timing in the system. Writing to a counter deliberately would make no sense, so this kind of bug would need to be something unintentional like a typo. Seems very unlikely. I don't recall that ever happening in well over 100 small microcontroller projects.

  2. Final filtered and adjusted A/D values. A common thing to do is to have a interrupt routine handle readings from a A/D. I usually read analog values faster than necessary, then apply a little low-pass filtering. There is often also scaling and offset that get applied.

    For example, the A/D may be reading the 0 to 3 V output of a voltage divider to measure the 24 V supply. The many readings are run thru some filtering, then scaled so that the final value is in millivolts. If the supply is at 24.015 V, then the final value is 24015.

    The rest of the system just sees a live updated value indicating the supply voltage. It doesn't know nor need to care when exactly that is updated, especially since it is updated much more often than the low pass filter settling time.

    Again, a interface routine could be used, but you get very little benefit from that. Just using the global variable whenever you need the power supply voltage is much simpler. Remember that simplicity isn't just for the machine, but that simpler also means less chance of human error.

5
qo'shib qo'ydi
Albatta, siz inline qila olmaysiz, shunda tanlash oson emas. Tasdiqlangan funktsiyalar bilan (va oldingi C99 kompilyatorlari kengaytmalarni uzatishda qo'llab-quvvatlangan) aytish kerakligini aytmoqchi edim, ishlaydiganlarga nisbatan argument bo'lishi mumkin emas. O'rtacha maqbul optimallashtiruvchi derivat bilan siz xuddi shunday ishlab chiqarilgan assemble bilan yakunlashingiz kerak.
qo'shib qo'ydi muallif Readonly, manba
Sizning fikringiz Olinga tegishli bo'lsa-da, lekin bu misollarda extern int ticks10ms ning inline int getTicks10ms() bilan almashtirilganligi boshqa yig'indisida mutlaqo farq qilmaydi qo'lda dasturning boshqa qismlarida tasodifan o'z qiymatini o'zgartirishi qiyin bo'ladi va bu chaqiriqqa "kanca" qilishning yo'lini beradi (masalan, birlik testida vaqtni mazax qilish, ushbu o'zgaruvchiga kirish uchun kirish yoki ). Agar siz bu o'zgaruvchini o'zgartirib, saniyalik programmerning imkoniyatini nolga teng deb hisoblasangiz ham, inline buyerning narxi yo'q.
qo'shib qo'ydi muallif Readonly, manba
@ Leroy105 Muammo "terrorchilar" ataylab global o'zgarmaydiganni suiiste'mol qilish emas. Nomlardagi ifloslanish katta loyihalarda muammo bo'lishi mumkin, ammo bu yaxshi nom bilan hal qilinishi mumkin. Yo'q, haqiqiy muammo butun dunyo o'zgaruvchisini maqsadga muvofiq ishlatishga urinayotgan dasturchi, lekin buni to'g'ri qilmaslikdir. Yoki ular barcha ISRlar bilan mavjud bo'lgan poyga holati masalasini tushunmaydilar yoki majburiy himoya qilish mexanizmini amalga oshirishni buzishgani yoki ular butun o'zgaruvchining kodi bo'yicha foydalanishlari sababli, ular qat'iy birlashma va o'qilmaydi kod.
qo'shib qo'ydi muallif Neil Foley, manba
@Groo: Bu sizning funktsiyalaringizni qo'llab-quvvatlaydigan tildan foydalanayotgan bo'lsangiz, haqiqatan ham to'g'ri bo'ladi va bu, getter funktsiyasining ta'rifini hamma uchun ko'rinishi kerak degan ma'noni anglatadi. Aslida yuqori darajali tilni qo'llayotganda, funktsiyani yanada ko'proq va global o'zgaruvchilardan foydalanaman. Yig'ilishda, global o'zgaruvchining qiymatini getter funktsiyasi bilan bezovta qilishdan ko'ra, juda osonroq.
qo'shib qo'ydi muallif Olin Lathrop, manba
Men sekin kechada davolanish uchun ketayotgan edim, albatta, mening kodimni o'rganishga harakat qilaman. Men Lundinning o'zgarmaydigan kirishni cheklash nuqtasini ko'raman, lekin mening haqiqiy tizimlarimga qarayman va bu uzoq masofadan turib, deb o'ylayman. HECh QAChON aslida tanqidiy global o'zgaruvchan tizimga aylanadi. Getter/Setter funktsiyalari sizni butun dunyo bo'ylab ishlatish uchun umumiy xarajatlar bilan yakunlanadi va ularni qabul qilish juda oddiy dasturlardir ...
qo'shib qo'ydi muallif Leroy105, manba

Har qanday muayyan to'siq global resurs bo'ladi. Ba'zan, bir nechta shovqinlarni bir xil kod bilan bo'lishish foydali bo'lishi mumkin. Misol uchun, tizimda bir nechta UART bo'lishi mumkin, ularning barchasi shu kabi jo'natish/qabul qilish mantiqidan foydalanishi kerak.

Buni amalga oshirish uchun yaxshi yondashuv, ob'ektni o'chirish moslamasi tomonidan ishlatiladigan narsalarni yoki ularga ko'rsatadigan narsalarni bir ob'ekt obyektiga joylashtirish va keyinchalik apparatni o'chirish ishlovchilariga o'xshash narsa bo'lishi kerak:

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

uart1_info , uart2_info va hokazo ob'ektlar umumiy o'zgaruvchan bo'lar edi, ammo ular faqat faqat interrupt ishlovchilari tomonidan ishlatiladigan global o'zgaruvchilar bo'ladi. Ishlovchilarning teginishlarini istagan barcha narsalar ularda muhokama qilinadi.

Shunga e'tibor beringki, har ikkalasiga ham, interrupt operatori va ota-kod yordamida erishish mumkin bo'lgan har qanday kod volatile bo'lishi kerak. O'chirish moslamasi tomonidan ishlatiladigan hamma narsani uchuvchi deb e'lon qilish oddiy bo'lishi mumkin, lekin agar ishlash muhim bo'lsa, ma'lumotni vaqtinchalik qiymatlarga ko'chiradigan kodni yozish mumkin, keyin ularni yozadi. Misol uchun, yozishning o'rniga:

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

yozing:

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

Qadimgi yondashuv o'qish va tushunishni osonlashtirishi mumkin, ammo bu ikkinchisidan ko'ra samarasiz bo'ladi. Bu tashvish bo'ladimi, bu dasturga bog'liq.

2
qo'shib qo'ydi

Quyida uchta fikr mavjud:

Joylarni o'zgaruvchan statik sifatida bitta faylga cheklash uchun bildiring.

Bayroqning o'zgaruvchisini shaxsiy qilib belgilang va bayroq qiymatiga kirish uchun getter va setter funktsiyalaridan foydalaning.

Bayroq o'zgaruvchisi o'rniga semafor kabi signal beruvchi ob'ektidan foydalaning. ISR semaforni o'rnatishi/joylashtirishi kerak edi.

0
qo'shib qo'ydi

Interrupt (ya'ni, ishlovchilaringizga ko'rsatadigan vektor) global resursdir. Shunday qilib, agar siz suyakka yoki to'plamga bir necha o'zgaruvchini ishlatsangiz ham:

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

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

yoki "virtual" funktsiyaga ega ob'ektga yo'naltirilgan kod:

HandlerObject *obj;

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

... birinchi qadam boshqa ma'lumotlarga erishish uchun haqiqiy global (yoki kamida statik) o'zgaruvchiga ega bo'lishi kerak.

Bu mexanizmlar indireksiyani qo'shib qo'yadi, shuning uchun, odatda, bu o'chirish moslamasini oxirgi tsiklni siqish zarur bo'lsa, bu bajarilmaydi.

0
qo'shib qo'ydi
@ next-hack Tashakkur!
qo'shib qo'ydi muallif Julian, manba
bayroqni uchuvchi int * deb e'lon qilishingiz kerak.
qo'shib qo'ydi muallif agfa555, manba

Men hozirda Cortex M0/M4 uchun kodlashni va C ++ da foydalanadigan usuli (C ++ yorlig'i yo'q, shuning uchun bu javob yopiq mavzu bo'lishi mumkin) quyidagilar:

Biz tekshirgichning haqiqiy kesish vektorida saqlanadigan barcha interrupt xizmat tartibini o'z ichiga olgan CInterruptVectorTable classini ishlatamiz:

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

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

Sinf CInterruptVectorTable , chiqib ketish vektorlarining ajralmas qismini ijro etadi, shuning uchun ish vaqtida interrupt vektorlariga turli funktsiyalarni ulashingiz mumkin.

U sinf interfeysi shunga o'xshash:

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;
};

Vektorli stolda saqlangan funktsiyalarni bajarishingiz kerak, chunki vektor jadvali obyekt emas, chunki bu kodlovchi bu -yokinterni taqdim eta olmaydi. Ya'ni, bu muammoni hal qilish uchun CInterruptVectorTable ichidagi statik pThis -pointerimiz bor. Statik uzilish funktsiyalaridan biriga kirganida CInterruptVectorTable bitta obyekti a'zolaridan foydalanish uchun pThis -pointerga kirish mumkin.


Endi dasturda SetIsrCallbackfunction funksiyasidan foydalanishingiz mumkin. Agar static funktsiyasini ko'rsatuvchi funktsiyani taqdim etsangiz, bu interrupt amalga oshganda chaqiriladi. Ko'rsatkichlar InterruptVectorTable_t virtualVectorTable da saqlanadi.

Chiqib ketish funktsiyasini amalga oshirish quyidagicha ko'rinadi:

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

Shunday qilib, static usulini chaqiradi ( private bo'lishi mumkin), undan so'ng static code> - bu obyektning a'zo o'zgaruvchilariga kirish uchun bitta nuqta (bitta).

IInterruptHandler kabi qurish va interfeyslarni moslashtira olasiz va moslamalarni obyektlarga saqlashingiz mumkin, shuning uchun statik bu -pointerning barcha sinflar. (Ehtimol, bizning arxitekturamizni keyingi iteratsiya qilishda harakat qilamiz)

Boshqa yondashuv biz uchun yaxshi ishlaydi, chunki interrupt mashinistini amalga oshirishga ruxsat beradigan yagona ob'ektlar apparatni ajratish sathidan bo'lganlar va biz odatda faqat har bir apparat bloki uchun bitta ob'ektga egamiz, shuning uchun u static bu -pointers. Apparatni ajratish qatlami esa ICallback deb ataladigan interruptlarga yana bir ajralmaslikka imkon beradi, keyin apparat ustidagi qurilma sathida amalga oshiriladi.


Global ma'lumotlarga kirishni xohlaysizmi? Ishonchingiz komil, lekin siz kerakli global ma'lumotlarning ko'pi bilan this -pointers va interrupt funktsiyalari kabi shaxsiy ma'lumotlardan foydalanishingiz mumkin.

Bu o'q bilan yopishqoq emas, va u ortiqcha qo'shiladi. Ushbu yondashuv yordamida IO-Link stackini amalga oshirish uchun kurashasiz. Agar siz vaqt bilan juda qattiq bo'lmasangiz, bu hamma joylardan foydalanish mumkin bo'lgan global o'zgaruvchisiz foydalanishni to'xtatib turish va modullardagi muloqotni moslashuvchan tarzda ajratish uchun juda yaxshi ishlaydi.

0
qo'shib qo'ydi
"Shunday qilib siz turli funktsiyalarni ish vaqtida interrupt vektorlariga bog'lashingiz mumkin" Bu yomon fikrga o'xshaydi. Dasturning "siklomatic murakkabligi" faqat tomidan o'tadi. Barcha ishlatiladigan kombinatsiyalar sinovdan o'tkazilishi kerak edi, shuning uchun vaqtni ham, to'plamdagi nizolarni ham qo'llamaslik kerak. Imo juda cheklangan foydali bo'lgan xususiyat uchun ko'plab bosh og'rig'i. (Agar sizda bootloader bo'lsa, bu yana bir hikoya) Umuman olganda bu meta dasturlarni hidlaydi.
qo'shib qo'ydi muallif Neil Foley, manba
DMA bir narsa, interrupt vektorlarining ish vaqti belgilash butunlay boshqa narsa. DMA drayverini sozlashning o'zgaruvchanligi, ish vaqti davomida bajarilishi mantiqan. Vektorli jadval, u qadar emas.
qo'shib qo'ydi muallif Neil Foley, manba
@Lundin O'ylaymanki, bizda bu borada turli fikrlar bor, biz bu haqda suhbatni boshlashimiz mumkin, chunki men sizning muammoingizni hali ham ko'rmayapman, shuning uchun mening javobim shu qadar yomon yozilganki, butun tushuncha noto'g'ri tushunilgan bo'lishi mumkin.
qo'shib qo'ydi muallif Arsenal, manba
@Lundin Men sizning fikringizni chindan ham ko'rmayapman. Misol uchun, agar DMA SPI uchun ishlatilayotgan bo'lsa va UART uchun ishlatilayotgan bo'lsa, UART kesuvchi ishlov beruvchisi uchun SPI to'xtatish moslamasi uchun DMA intervalini bog'lash uchun foydalanamiz. Ikkala ishlovchilar ham sinovdan o'tishi kerak, ammo bu muammo emas. Va, albatta, metan dasturlash bilan aloqasi yo'q.
qo'shib qo'ydi muallif Arsenal, manba