Як пазбегнуць глабальных зменных пры выкарыстанні перапыненняў ва ўбудавальных сістэмах

Ёсць добры спосаб ажыццяўлення сувязі паміж ISR і астатняй часткай праграмы для ўбудаванай сістэмы, якая дазваляе пазбегнуць глабальных зменных?

Здаецца, што агульная карціна павінна мець глабальную зменную, якая падзяляецца паміж ISR і астатняй частцы праграмы і выкарыстоўваецца як сцяг, але гэта выкарыстанне глабальных зменных ідзе насуперак са мной. Я ўключыў просты прыклад выкарыстання ISRS Арн-Libc стылі:

volatile uint8_t flag;

int main() {
    ...

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

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

Я не магу бачыць далёка вакол таго, што, па сутнасці, пытанне агляднай; любыя зменныя, даступныя на абодвух ISR і астатняй часткай праграмы павінна быць глабальнай па сваёй сутнасці, вядома? Нягледзячы на ​​гэта, я часта бачыў, як людзі кажуць нешта ўздоўж ліній «глабальных зменных адзін спосаб рэалізацыі сувязі паміж ISR, і астатняй часткай праграмы» (курсіў мой), які, як уяўляецца, мае на ўвазе, што існуюць і іншыя метады; калі ёсць іншыя метады, якія яны?

9
побач, сцяг павінен быць абвешчаны зменлівы, таму што вы карыстаецеся/змяніць яго па-за нармальнага патоку праграмы. Гэта прымушае кампілятар ня аптымізаваць любы чытання/запісы для сцяга, а таксама выконваць уласна аперацыі чытання/запісы.
дададзена аўтар agfa555, крыніца
Гэта не абавязкова, што ўсе астатнія праграмы будуць мець доступ; калі вы абвясцілі зменную як статычныя, толькі файл, у якім была абвешчаная пераменная будзе бачыць. Гэта зусім не цяжка мець зменныя, якія бачныя ў цэлым аднаго файла, але не астатняя частка праграмы, і гэта можа дапамагчы.
дададзена аўтар Silicomancer, крыніца
@ Наступнага хак Так, гэта абсалютна правільна, прабачце, я проста спрабаваў прыдумаць прыклад хутка.
дададзена аўтар user109324, крыніца

6 адказы

Існуе стандарт дэ-факта спосаб зрабіць гэта (мяркуючы, што праграмаванне C):

    <�Літый> Перапыненне/ЗРМС мае нізкі ўзровень і, такім чынам, павінна быць рэалізавана толькі ўнутры драйвера, звязаны з апаратнымі сродкамі, які генеруе перапыненне. Яны не павінны быць размешчаны ў іншым месцы, але ўнутры гэтага драйвера.
  • Усе камунікацыі з ISR ажыццяўляюцца кіроўцам і толькі кіроўца. Калі іншыя часткі праграмы неабходны доступ да гэтай інфармацыі, ён павінен запытаць яго ў кіроўцы праз функцыі сетэр/здабытчыка ці аналагічны.
  • Вы не павінны аб'яўляць «глабальныя» зменныя. Глабальныя зменныя Scope значэнне файла са знешнім звязваннем. Гэта значыць :. Зменныя, якія могуць быць прыцягнутыя з экстэрнам ключавое слова або проста па памылцы
  • Замест таго, каб прымусіць прыватны інкапсуляцыю ўнутры кіроўцы, усе такія зменныя, падзяляем паміж кіроўцам і ISR павінна быць аб'яўлена статычным . Такая пераменная <�ет> не глабальны, але абмежаваны ў файле, дзе яна абвешчаная.
  • Каб прадухіліць праблемы аптымізацыі кампілятара, такія variabels таксама павінны быць абвешчаныя як лятучага . Заўвага: гэта не дае атамарным доступ або дазволіць паўторнае entrancy
  • Некаторыя спосаб механізму паўторнага entrancy часта патрабуецца ў драйверы, у выпадку ISR запісвае ў зменную. Прыклады: перапыненне адключыць, глабальную маску перапынення, семафор/семафора або гарантавана атамным чытаюць
  • .
12
дададзена
Заўвага: Вы, магчыма, прыйдзецца выставіць функцыю ISR прататып праз загаловак, для таго, каб змясціць яго ў вектарнай табліцы, размешчанай у іншым файле. Але гэта не з'яўляецца праблемай да тых часоў, як вы дакументальна пацвердзіць, што гэта перапыненне і не павінен выклікацца праграмай.
дададзена аўтар Neil Foley, крыніца
@ Leroy105 мова Сі падтрымліваюць убудаваныя функцыі для вечнасці цяпер. Хоць нават выкарыстанне инлайн састарваецца, так як складальнікі становяцца разумней і разумней пры аптымізацыі кода. Я б сказаў, што заклапочанасць з нагоды накладных выдаткаў з'яўляецца «да сталай аптымізацыю" - у большасці выпадкаў накладныя выдаткі не мае значэння, калі гэта наогул нават прысутнічае ў машынным кодзе.
дададзена аўтар Neil Foley, крыніца
Гэта, як гаворыцца, у выпадку напісання драйвераў ISR, некаторыя 80-90% усіх праграмістаў (не перабольшваю) заўсёды атрымаць нешта ў іх не так. Вынік тонкія памылкі: няправільна чысціцца сцягі, некарэктная аптымізацыя кампілятара з-за адсутнасць лятучага, умовы гонкі, паршывы прадукцыйнасць у рэжыме рэальнага часу, перапаўненне стэка і г.д. і да т.п. У выпадку ISR ня правільна інкапсуляванага ўнутры кіроўцы, верагоднасць такіх тонкіх багаў дадаткова павялічана. Засяродзьцеся на напісанне Буг-драйвер, перш чым турбавацца аб тым, перыферычнага цікавасць, напрыклад, калі сетэр/здабытчыкамі ўвесці ледзь-ледзь над галавой.
дададзена аўтар Neil Foley, крыніца
Што б вы сказалі, калі контраргумент быў ўзрослая над галавой (і дадатковы код) з дапамогай сетэр/атрымання функцыі? Я ішоў па гэтай нагоды сябе, думаючы аб стандартах кода для нашых 8 разрадных ўбудавальных прылад.
дададзена аўтар Leroy105, крыніца
гэта выкарыстанне глабальных зменных ідзе насуперак са мной

Гэта рэальная праблема. Атрымаць над ім.

Зараз перад каленнымі jerkers адразу гучных слоў пра тое, як гэта нячыстае, дазвольце мне зрабіць заўвагу, што няшмат. Існуе, вядома, небяспека ў выкарыстанні глабальных зменных у лішку. Але яны могуць таксама павысіць эфектыўнасць, што часам мае значэнне ў невялікіх абмежаваных рэсурсаў сістэмы.

Ключ павінен думаць пра тое, калі вы можаце разумна выкарыстоўваць іх, і наўрад ці, каб атрымаць сабе ў бяду, у параўнанні з памылкай толькі і чакаюць, каб здарыцца. Ёсць заўсёды кампрамісы. У той час як <�я> наогул </я> пазбегнуць глабальных зменныя для абмену дадзеных паміж перарываннем і пярэднім планам кодай з'яўляецца undertandable кіраўніцтва, прымаючы яго, як і большасць іншых прынцыпаў, да рэлігій крайнасці контрпрадуктыўна.

Некаторыя прыкладамі, дзе я часам выкарыстоўваць глабальныя зменныя для перадачы інфармацыі паміж перарываннем і пярэднім планам кодам з'яўляюцца:

  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
дададзена
Вядома, калі вы не можаце ўбудоўваць, то выбар не так проста. Я хацеў сказаць, што з убудавальнымі функцыі (і многія кампілятары папярэдне C99 ўжо падтрымліваюцца пашырэнне ўбудавання), прадукцыйнасць не можа быць аргументам супраць здабытчыкаў. Пры разумным які аптымізуе кампілятар, вы павінны ў канчатковым выніку з той жа вырабленай зборкі.
дададзена аўтар Readonly, крыніца
Вашы балы сапраўдныя Олін, але нават у гэтых прыкладах, замяняючы ехЬегп INT ticks10ms з убудаваны Int getTicks10ms() не будзе мець абсалютна ніякай розніцы ў скампіляваных зборкі, а з другога рукі ён будзе рабіць цяжка выпадкова змяніць яго значэнне ў іншых частках праграмы, а таксама дазволіць вам спосаб «кручок» для гэтага выкліку (напрыклад, для імітацыі часу падчас модульнага тэставання, каб увайсці доступ да гэтай зменнай, ці нешта ). Нават калі вы сцвярджаеце, што шанец на сан праграміста змяніць гэтую зменную ў нуль, няма ніякай кошту инлайн здабытчыка.
дададзена аўтар Readonly, крыніца
@ Leroy105 Праблема не «тэрарысты» наўмысна злоўжываюць глабальную зменную. Забруджванне прастор імёнаў можа быць праблемай у вялікіх праектах, але якія могуць быць вырашаны з добрым імем. Не, сапраўдная праблема заключаецца праграміст спрабуе выкарыстоўваць глабальную зменную, як задумана, але не ў стане зрабіць гэта правільна. Альбо таму, што яны не разумеюць гэтае пытанне гонкі ўмовы, якое існуе з усім ЗРМСОМ, ці таму, што яны заблыталіся ажыццяўленнем абавязковага механізму абароны, ці проста таму, што яны вывяргаюць выкарыстанне глабальных пераменнага ва ўсім кодзе, ствараючы цесную сувязь і нечытэльны код.
дададзена аўтар Neil Foley, крыніца
@Groo: Гэта толькі справядліва, калі вы выкарыстоўваеце мову, які падтрымлівае функцыю ўбудавання, і гэта азначае, што вызначэнне функцыі геттера павінна быць бачнымі для ўсіх. На самай справе пры выкарыстанні мовы высокага ўзроўню, я выкарыстоўваю геттерные функцыі больш і менш глабальныя зменныя. Пры зборцы, гэта проста нашмат прасцей, каб захапіць значэнне глабальнай зменнай, чым затлумляцца з функцыяй геттерного.
дададзена аўтар Olin Lathrop, крыніца
Я ішоў да тэрапіі, у павольных тыдня, на самай справе спрабую прыдзірацца маім код. Я разумею кропку Лундина на абмежаванне доступу да пераменнага, але я гляджу на свае рэальныя сістэмы і думаю, што гэта такая аддаленая магчымасць АСОБЫ б фактычна Jank сістэму крытычных глабальныя зменнай. Функцыі/сетэр Getter ў канчатковым выніку каштаваць вам накладных расходаў у параўнанні з выкарыстаннем толькі глабальнай і прымаючы гэтыя даволі простыя праграмы ...
дададзена аўтар Leroy105, крыніца

Любое канкрэтнае перапыненне будзе глабальным рэсурсам. Часам, аднак, гэта можа быць карысна мець некалькі перапыненняў адных і той жа код. Напрыклад, сістэма можа мець некалькі УАПП, усе з якіх павінны выкарыстоўваць аналагічныя адпраўкі/атрымання логікі.

Добры падыход для апрацоўкі, які павінен змясціць рэчы, якія выкарыстоўваюцца апрацоўшчыкам перапыненняў, або паказальнікі на іх у аб'екце структуры, а затым фактычныя апрацоўшчыкі апаратных перапыненняў быць нешта накшталт:

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 і г.д. б глабальныя зменныя, але яны былі б толькі глабальныя зменныя, якія выкарыстоўваюцца апрацоўшчыкі перапыненняў. Усё астатняе, што апрацоўшчыкі крануты будзе апрацоўвацца ў тых.

Звярніце ўвагу, што ўсе, доступ да якой ажыццяўляецца як з дапамогай апрацоўшчыка перапыненняў і кодам магістральнага павінны быць кваліфікаваная лятучага . Гэта можа быць прасцей проста абвясціць як лятучага усё, што будзе выкарыстоўвацца на ўсіх апрацоўшчыкам перапыненняў, але калі прадукцыйнасць важная адна можа спатрэбіцца, каб напісаць код, які капіюе інфармацыю для часовых значэнняў, дзейнічае на іх, а затым запісвае іх назад. Напрыклад, замест:

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

напісаць:

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

Першы падыход можа быць прасцей чытаць і разумець, але будзе менш эфектыўным, чым другі. Незалежна ад таго, што гэта праблема будзе залежаць ад ужывання.

2
дададзена

Вось тры ідэі:

Абвясьцеце зменную сцяг, як статычны, каб абмежаваць магчымасці для аднаго файла.

Зрабіце сцяг зменнай прыватных і выкарыстанне метады атрымання і ўстаноўкі функцый для доступу да значэння сцяга.

Выкарыстоўвайце аб'ект сігналізацыі, такія як семафор замест пераменнага сцяга. ISR б ўсталяваць/апублікаваць семафор.

0
дададзена

Перапыненне (г.зн. вектар, які паказвае на апрацоўшчык) з'яўляецца глабальным рэсурсам. Такім чынам, нават калі вы выкарыстоўваеце некаторыя зменныя ў стэку або ў кучы:

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

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

або аб'ектна-арыентаваны код з «віртуальнай» функцыі:

HandlerObject *obj;

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

... першы крок павінен уключаць фактычныя глабальныя (ці, па меншай меры, статычная) зменную, каб дасягнуць таго, каб іншыя дадзеныя.

Усе гэтыя механізмы дадаць вакольныя, так што гэта, як правіла, не адбываецца, калi вы хочаце, каб сціснуць апошні цыкл з апрацоўшчыка перапыненняў.

0
дададзена
@ Наступнага хак дзякуй!
дададзена аўтар Julian, крыніца
вы павінны абвясьціць сцяг A S лятучага Int *.
дададзена аўтар agfa555, крыніца

Я кадуецца Cortex M0/M4 ў дадзены момант, і падыход, які мы выкарыстоўваем у C ++ (не C ++ тэг, так што гэты адказ можа быць не па тэме) заключаецца ў наступным:

Мы выкарыстоўваем клас CInterruptVectorTable , які змяшчае ўсе падпраграмы абслугоўвання перапыненняў, якія захоўваюцца ў сапраўдным вектары перапынення кантролера:

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

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

Клас CInterruptVectorTable рэалізуе абстракцыю вектараў перапыненняў, так што вы можаце звязаць розныя функцыі для вектараў перапыненняў падчас выканання.

Інтэрфейс гэтага класа выглядае наступным чынам:

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

Вам трэба зрабіць функцыі, якія захоўваюцца ў табліцы вектараў статычнаму , таму што кантролер не можа забяспечыць гэта -указатель як табліца вектараў не з'яўляецца аб'ектам. Такім чынам, каб абыйсці гэтую праблему, мы маем статычны pThis -указатель ўнутры CInterruptVectorTable . Пры ўваходзе ў адзін з статычных функцый перапынення, ён можа атрымаць доступ да pThis -указатель, каб атрымаць доступ да членаў аднаго аб'екта CInterruptVectorTable .


Цяпер у праграме, вы можаце выкарыстоўваць SetIsrCallbackfunction , каб забяспечыць паказальнік на функцыю, а статычным функцыя, якая будзе выклікана, калі адбываецца перапыненне. Паказальнікі захоўваюцца ў InterruptVectorTable_t virtualVectorTable .

А рэалізацыя функцыі перапынення выглядае наступным чынам:

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

Так што будзем называць статычны метад іншага класа (які можа быць прыватным ), які затым можа ўтрымліваць іншы статычны гэта -указатель, каб атрымаць доступ да пераменным-сябрам гэтага аб'екта (толькі адзін).

Я думаю, вы маглі б пабудаваць і інтэрфейс, як IInterruptHandler і захоўваць паказальнікі на аб'екты, так што вам не трэба статычны гэта -указатель ва ўсіх тых, класы. (Магчыма, мы стараемся, што ў наступнай ітэрацыі нашай архітэктуры)

Іншы падыход працуе добра для нас, як толькі аб'екты, дазволеныя для рэалізацыі апрацоўшчыка перапыненняў з'яўляюцца тыя, у пласт апаратнай абстракцыі, і мы, як правіла, толькі адзін аб'ект для кожнага апаратнага блока, так што гэта добра працуе з статычнаму </код > гэта -pointers. А абстракцыя апаратных сродкаў пласт забяспечвае яшчэ адзін абстракцыю для перапынення, званага <�кодам> ICallback </кодам> які затым рэалізуецца ў пласце прылады над апаратнымі сродкамі.


Ці ёсць у вас доступ да глабальных дадзеных? Вядома, вы робіце, але вы можаце зрабіць вялікую частку неабходнага глабальнага прыватных дадзеных як гэта -pointers і функцыі перапынення.

Гэта не куленепрабівальныя, і гэта дадае накладныя выдаткі. Вы будзеце змагацца, каб рэалізаваць стэк IO-Link, выкарыстоўваючы гэты падыход. Але калі вы не вельмі туга з таймінгамі, гэта працуе дастаткова добра, каб атрымаць гнуткую абстракцыю перапыненняў і сувязі ў модулях без выкарыстання глабальных зменных, якія даступныя з любой кропкі планеты.

0
дададзена
«Так што вы можаце звязаць розныя функцыі да вектараў перапыненняў падчас выканання» Гэта гучыць як дрэнная ідэя. «Цикломатическое складанасць» праграма будзе проста прайсці праз дах. Усе камбінацыі прэцэдэнтаў павінны быць правераны, так што не існуе ні тэрміны, ні стэка канфліктаў выкарыстанне. Шмат галаўнога боль для функцыі з вельмі абмежаванай карыснасцю ИМО. (Калі вы не маеце справу загрузніка, гэта іншая гісторыя) У цэлым гэта пахне мета-праграмавання.
дададзена аўтар Neil Foley, крыніца
DMA гэта адно, прызначэнне часу выканання вектараў перапыненняў гэта нешта зусім іншае. Гэта мае сэнс, каб дазволіць ўсталёўцы драйвера DMA быць зменнай, у рэальны час. Вектар стол, не так шмат.
дададзена аўтар Neil Foley, крыніца
@Lundin Я думаю, у нас розныя погляды на гэта, мы маглі б пачаць размову пра гэта, таму што я да гэтага часу не бачу вашу праблему з ім - так што можа быць мой адказ так дрэнна напісана, што ўся канцэпцыя няправільна.
дададзена аўтар Arsenal, крыніца
@Lundin я не бачу вашу кропку. Мы выкарыстоўваем яго, каб звязаць, напрыклад, DMA перапынення апрацоўшчык перапынення SPI, калі DMA выкарыстоўваецца для SPI і апрацоўшчыка перапынення UART, калі яно выкарыстоўваецца для UART. Абодва апрацоўшчыка павінен быць праверана, вядома, але не праблема. І гэта, вядома, не мае нічога агульнага з мета-праграмавання.
дададзена аўтар Arsenal, крыніца