Բովանդակություն:

Մաս 1 ARM Assembly TI RSLK Robotics Learning Programriculum Lab 7 STM32 Nucleo: 16 Steps
Մաս 1 ARM Assembly TI RSLK Robotics Learning Programriculum Lab 7 STM32 Nucleo: 16 Steps

Video: Մաս 1 ARM Assembly TI RSLK Robotics Learning Programriculum Lab 7 STM32 Nucleo: 16 Steps

Video: Մաս 1 ARM Assembly TI RSLK Robotics Learning Programriculum Lab 7 STM32 Nucleo: 16 Steps
Video: TI Robotics System Learning Kit - Texas Instruments and Mouser Electronics 2024, Հուլիսի
Anonim
Image
Image

Այս Instructable- ի ուշադրության կենտրոնում է STM32 Nucleo միկրոհսկիչը: Դրա շարժառիթը `կարողանալ մերկ ոսկորներից հավաքման նախագիծ ստեղծել: Սա կօգնի մեզ ավելի խորանալ և հասկանալ MSP432 Launchpad նախագիծը (TI-RSLK), որն արդեն մի քանի Instructables- ի թեման էր:

Առցանց շատ օգնություն չկա MSP432- ի համար հավաքների համար նախագիծ ստեղծելու համար `օգտագործելով Code Composer Studio- ն: Մինչ այժմ մենք պարզապես պատճենում/տեղադրում էինք արդեն գոյություն ունեցող հավաքման նախագծից: Այս մոտեցումը մեզ լավ ծառայեց:

Այնուամենայնիվ, այժմ, Lab 7 -ի համար, մենք բախվել ենք մի փոքր խնդրի: Կամ գոնե ժամանակավոր խարխափում: Լաբորատորիա 7-ը ներկայացնում է վերջնական վիճակի մեքենաներ, և առաջին բանը, որին հանդիպում ենք, արժեքների զանգված ստեղծելու և օգտագործելու անհրաժեշտությունն է: Քանի որ ԹԻ դասընթացը հիմնականում օգտագործում է C ծրագրավորում, սա խնդիր չէ: Բայց այս հրահանգները կենտրոնացած են հավաքման վրա, այլ ոչ թե C.

Ավելին, քանի որ զանգվածը միայն կարդալու արժեքներ ունի, լավ կլինի այն տեղադրել ֆլեշ հիշողության մեջ, այլ ոչ թե RAM- ի:

Թվում է, թե շատ ավելի շատ օգնություն կա առցանց ՝ STM32 MCU- ի միջոցով հավաքների նախագծերի համար, ուստի, մենք սկսում ենք այս Instructable- ով ՝ սովորածն օգտագործելու նպատակով, այնուհետև դիմելու MSP432- ին և Code Composer Studio- ին:

Այդ նպատակին հասնելու ճանապարհին մենք փորձ կունենանք ձեռք բերել ևս մեկ, հանրաճանաչ միկրոկառավարիչի հետ:

Քայլ 1: Սարքի նախնական փորձարկում

Սարքի նախնական փորձարկում
Սարքի նախնական փորձարկում
Սարքի նախնական փորձարկում
Սարքի նախնական փորձարկում
Սարքի նախնական փորձարկում
Սարքի նախնական փորձարկում

Կրկին, ինչու՞ հատկապես ընտրել STM32 Nucleo- ն:

Ազնիվ խոսք? Քանի որ ես փնտրում էի լավ հոդվածներ ARM վերահսկիչների համար մերկ մետաղների հավաքման նախագծերի վերաբերյալ և հանդիպեցի այս շարքին: Եվ նաև այն պատճառով, որ STM32- ը կարծես հանրաճանաչ MCU է:

Ես որոշ հետազոտություններ կատարեցի (ընտրելու շատ տարբերակներ կան - տես վերևի պատկերը), բայց ի վերջո այն դարձավ այն, ինչ իրականում կարող եմ ստանալ, քանի որ ես պատրաստվում էի օգտագործել Amazon- ը (ԱՄՆ -ում):

Այն գալիս է պարզ, բայց պրոֆեսիոնալ փաթեթում ՝ գործարկման որոշ հրահանգներով: Մի փոքր ծիծաղելի էր տեսնել, որ վերահսկիչի վրա այրված ցուցադրական ցուցադրումը գրեթե այն էր, ինչ մենք արել էինք անցյալի Instructables- ում. LED- ը թարթում է և արագությունը փոխում է ՝ ըստ կոճակի սեղմման:

Թվում է, որ այս զարգացման տախտակը շատ նման է MSP432- ին նրանով, որ կան 2 լուսադիոդային լուսարձակներ և մեկ օգտագործողի կոճակ: MSP432- ն ունի 2 օգտվողի կոճակ:

Ինչպես տեսնում եք լուսանկարներում, ես մի փոքր զարմացա, որ տախտակն ունի մինի և ոչ միկրո USB: Պետք էր վերջանալ լարը գնելու համար:

Մեկ այլ լավ փորձություն այն է, որ երբ այն միացնում եք ձեր համակարգչին (ես օգտագործում եմ Linux տուփ), այն հայտնվում է իմ ֆայլերի կառավարիչում ՝ որպես ֆայլային համակարգ, որը կոչվում է «NODE_F303RE»: Բացում, որը բացահայտում է երկու ֆայլ ՝ մեկ HTML և մեկ տեքստ:

Վերջ, բայց գոնե ասում է, որ կապը բավականին հեշտ է թվում:

Այժմ մենք պատրաստ ենք սկսել:

Ես կփորձեմ չկրկնել IVONOMICON Bare Metal հոդվածաշարի լավ տեղեկություններից որևէ մեկը, այլ ավելի շուտ ավելացնել այն:

Քայլ 2: Հիմնականը

Առաջին բանը, որ մեզ պետք է, կազմողն է:

Եվ հետո, մեզ անհրաժեշտ է վրիպազերծիչ.

devchu@chubox: ~ $ sudo apt-get install gdb-arm-none-eabi Փաթեթների ցուցակների ընթերցում … Կատարված է Կախվածության ծառը կարդում է պետական տեղեկատվությունը… Կատարված Կտեղադրվեն հետևյալ նոր փաթեթները ՝ gdb-arm-none-eabi 0 արդիականացված, 1 նոր տեղադրված, 0 -ը ՝ հեռացնելու և 8 -ը ՝ ոչ արդիականացված: Անհրաժեշտ է ստանալ 2, 722 կԲ արխիվ: Այս գործողությունից հետո կօգտագործվի 7, 738 կԲ լրացուցիչ սկավառակի տարածք: Ստացեք. կԲ/վ) Նախկինում չընտրված փաթեթի ընտրություն gdb-arm-none-eabi. (Տվյալների ընթերցում… 262428 ֆայլ և գրացուցակ, որոնք այժմ տեղադրված են): Պատրաստվում է…/gdb-arm-none-eabi_7.10-1ubuntu3+9_amd64.deb… triggers for man-db (2.7.5-1)… gdb-arm-none-eabi (7.10-1ubuntu3+9) կարգավորում…

Քայլ 3: Հիմնականը `Windows

Վերոնշյալ քայլը ենթադրում էր, որ մենք օգտագործում ենք Linux: Իսկ եթե մենք օգտագործում ենք Windows?

Կարող եք գնալ arm Developer կայք, և ներբեռնման մի քանի տարբերակ կա: Ես օգտագործում եմ Windows 8 մեքենա:

Տեղադրման ընթացքում ես ընտրեցի այն տեղադրել «C: \» արմատային սկավառակի վրա Program Files- ի փոխարեն միայն այն պատճառով, որ ես նաև cygwin եմ օգտագործում, և ավելի հեշտ էր իմ տեղական աղբարկղից դեպի C: խառնաշփոթ ծրագրի ֆայլերի ճանապարհին (բացատներով և այլն):

Այսպիսով, իմ cygwin միջավայրը և ուղին և այլն, այսպիսին են թվում.

C: / cygwin64 / home / bin / arm-none-eabi-gcc, որտեղ arm-none-eabi-gcc- ը հղում է C: / GNUToolsArmEmbedded / 7.2018.q2.update / bin / arm-none-eabi- gcc

Այնուհետև ես ստեղծեցի «dev» պանակ ՝ cygwin home- ի ներքո, և այնտեղ տեղադրեցի core. S ֆայլը և գործարկեցի կոմպիլյատոր հրամանը: (տե՛ս ստորև ՝ կազմող նյութերի համար):

Ես ճիշտ նույն բանն արեցի gdb- ի համար (arm-none-eabi-gdb):

Քայլ 4: Որոնք են էականները

Այսպիսով, ինչ է «gcc-arm-none-eabi»:

Gnu կոմպիլյատորը (GCC) ծրագրավորման լեզուները (ինչպես C) կկազմի մայր կոդի մեջ այն սարքի համար, որի վրա այն աշխատում է: Օրինակ, եթե դուք կազմեք C կոդ ՝ GCC- ի միջոցով ձեր Windows մեքենայի վրա, այն կառուցված կլինի Windows մեքենայի վրա աշխատելու համար: Ստեղծվող գործարկվողը (սովորաբար) չի աշխատի ARM միկրոկառավարիչի վրա:

Այսպիսով, ARM միկրոկառավարիչում ներբեռնվող և այրվող ծրագրեր կառուցելու համար (մեր ներկայիս դեպքում դա կլինի STM32 Nucelo- ն), մենք պետք է GCC- ին այլ բան տանք ՝ «խաչաձև կազմելու» հնարավորություն: Այսինքն ՝ գործարկելի ստեղծելու ունակություն ոչ թե իր հարազատ համակարգի (և պրոցեսորի), այլ թիրախային համակարգի (ARM միկրոկառավարիչ) համար: Հենց այստեղ է, որ գործում է «gcc-arm-none-eabi»:

Այսպիսով, ի՞նչ է «gdb-arm-none-eabi»:

Երբ մենք ներբեռնում և այրում ենք (բռնկում) նոր առաջադրվող գործարկիչը միկրոհսկիչի մեջ, հավանաբար կցանկանանք կարգաբերել այն ՝ քայլ առ տող կոդով: GDB- ն gnu- ի վրիպազերծիչն է, և այն նույնպես կարիք ունի իր գործն անելու եղանակի, սակայն ուղղված է այլ համակարգի:

Այսպիսով, gdb-arm-none-eabi է GDB- ին, ինչ gcc-arm-none-eabi է GCC- ին:

Մեկ այլ առաջարկվող փաթեթի տեղադրում էր «libnewlib-arm-none-eabi»: Ո՞րն է այդ մեկը:

Newlib- ը C գրադարան և մաթեմատիկական գրադարան է, որը նախատեսված է ներկառուցված համակարգերում օգտագործելու համար: Այն գրադարանային մի քանի մասերի համախմբում է, բոլորը ՝ ազատ ծրագրային ապահովման լիցենզիաներով, որոնք դրանք հեշտությամբ կիրառելի են ներկառուցված արտադրանքի վրա:

Եվ վերջապես, «libstdc ++-arm-none-eabi» փաթեթը: Դա բավականին ակնհայտ է. դա C ++ գրադարան է խաչաձև կազմողի համար. ներդրված ARM միկրոհսկիչների համար:

Քայլ 5: Linker ֆայլը

Linker ֆայլը
Linker ֆայլը
Linker ֆայլը
Linker ֆայլը

Եկեք ստեղծենք կապող սցենար:

Այս ֆայլի հիմնական առանցքը կամ բլոկը կլինի MEMORY հրամանը:

--- sourceware.org- ից:

Կապողի կանխադրված կազմաձևը թույլ է տալիս տեղաբաշխել առկա բոլոր հիշողությունը: Դուք կարող եք անտեսել սա ՝ օգտագործելով MEMORY հրամանը: MEMORY հրամանը նկարագրում է թիրախում հիշողության բլոկների տեղն ու չափը: Դուք կարող եք օգտագործել այն նկարագրելու համար, թե հիշողության որ շրջանները կարող են օգտագործվել կապողի կողմից, և ո՞ր հիշողության շրջաններից պետք է խուսափի: Այնուհետև կարող եք բաժիններ հատկացնել հիշողության որոշակի շրջաններին: Կապողը բաժնի հասցեներ է սահմանում `հիմնված հիշողության շրջանների վրա և նախազգուշացնում է այն տարածքների մասին, որոնք չափազանց լի են: Կապիչը չի խառնի հատվածները ՝ տեղավորվելու համար մատչելի շրջաններում: Կապող սցենարը կարող է պարունակել MEMORY հրահանգի բազմաթիվ օգտագործում, սակայն, սահմանված բոլոր հիշողության բլոկները վերաբերվում են այնպես, կարծես դրանք նշված են մեկ MEMORY հրամանի ներսում: MEMORY- ի շարահյուսությունը հետևյալն է.:

ՀԻՇՈՈԹՅՈՆ

{name [(attr)]: ORIGIN = ծագում, LENGTH = len…}

Օրինակ հոդվածում.

/* Սահմանեք RAM- ի ավարտը և կույտի հիշողության սահմանը* //* (4KB SRAM STM32F031x6 տողում, 4096 = 0x1000)*//* (RAM- ը սկսվում է 0x20000000 հասցեից) _estack = 0x20001000;

ՀԻՇՈՈԹՅՈՆ

{FLASH (rx): ORIGIN = 0x08000000, LENGTH = 32K RAM (rxw): ORIGIN = 0x20000000, LENGTH = 4K}

Այսպիսով, մենք պետք է պարզենք, թե որքան FLASH (մեր ծրագրի և հաստատունների համար և այլն) և որքան RAM (ծրագրի կողմից օգտագործման համար. Կույտ և կույտ և այլն) մեր հատուկ տախտակի համար: Սա մի փոքր հետաքրքիր է դառնում:

Nucleo- ի հետ բերված գեղեցիկ փոքրիկ քարտը ասում է, որ այն ունի ֆլեշ հիշողություն `512 Կբայթ, իսկ SRAM- ը` 80 Կբայթ: Այնուամենայնիվ, այն USB- ին միացնելով, այն տեղադրվում է որպես երկու ֆայլ ունեցող ֆայլային համակարգ, և ֆայլերի կառավարիչը և GParted- ը նշում են, որ այն ունի ավելի քան 540 Կբայթ տարածք: (RAM?)

ԲԱՅ,, փորձելով ջնջել երկու ֆայլերը ֆայլերի կառավարչի միջոցով, անջատել, ապա նորից միացնել սարքը, դեռ ցույց է տալիս երկու ֆայլը: (և ֆայլերի կառավարիչը ինչ -որ բան ճանաչեց, քանի որ յուրաքանչյուր ֆայլի վրա կա մի փոքրիկ «կողպման» պատկերակ:

Այսպիսով, եկեք գնանք քարտի թվերով: Այսպիսով, այժմ մենք վերցնում ենք վերը նշված օրինակը և այն վերածում ենք մեր հատուկ տախտակի:

Կարող եք օգտագործել այս առցանց հիշողության փոխարկիչի նման մի բան ՝ ընդհանուր KB- ից որոշակի քանակի բայթ անցնելու համար:

Այնուհետև կարող եք օգտագործել առցանց տասնորդական վեցանկյուն փոխարկիչ:

/ * Սահմանեք RAM- ի ավարտը և կույտի հիշողության սահմանը */

/* (4KB SRAM STM32F031x6 տողում, 4096 = 0x1000)* //* օրինակը*/

/ * քայլ 1: (80KB SRAM STM32F303RE- ում, 81920 = 0x14000) * // * մեր տախտակը */

/* քայլ 2, վեցանկյուն չափը ավելացնել վեցանկյուն մեկնարկային հասցեին (ստորև): */

/ * (RAM- ը սկսվում է 0x20000000 հասցեից) */

_estack = 0x20001000; / * օրինակը */

_estack = 0x20014000; / * մեր խորհուրդը */

ՀԻՇՈՈԹՅՈՆ {

ԼՈASՅՍ (rx). ORIGIN = 0x08000000, LENGTH = 512K

RAM (rxw) ՝ ORIGIN = 0x20000000, LENGTH = 80K

}

Եկեք վերը նշված ֆայլը կոչենք «linker.script.ld»:

Քայլ 6: Վեկտորային աղյուսակ

Վեկտորային աղյուսակ
Վեկտորային աղյուսակ

Այժմ մենք պատրաստվում ենք ստեղծել մի փոքրիկ հավաքման ֆայլ (հրահանգներով) `որոշ շատ հիմնական ընդհատումների մշակման համար: Մենք կհետևենք հոդվածի օրինակին և կստեղծենք «core. S» անունով ֆայլ:

Կրկին, ահա ֆայլի օրինակելի բովանդակությունը, բայց ես փոփոխություն կատարեցի մեր հատուկ տախտակի համար.

// Այս հրահանգները սահմանում են մեր չիպի և

// հավաքման լեզուն, որը մենք կօգտագործենք. փոխարենը ավելացրեք մեր տախտակի կեղևը: տե՛ս վերը նշված պատկերը այս քայլում * / /*.fpu softvfp * / / *մեկնաբանեք օրինակի այս տողը * /.fpu vfpv4 / *փոխարենը ավելացրեք մեր տախտակին; այն իսկապես ունի FPU */.thumb // Գլոբալ հիշողության վայրեր:.global vtable.global reset_handler / * * Փաստացի վեկտորային աղյուսակը: * Պարզության համար ներառված են միայն RAM- ի չափը և «վերագործարկումը» կարգավորիչը: */.տիպ vtable, %object vtable:.word _estack.word reset_handler.size vtable,.-vtable

Հմ.. Ոչ.. Համահունչ »հրահանգ

Այնուամենայնիվ, դա կրիտիկական չէ: Այդ մասին (գուցե) ավելի ուշ:

. շարահյուսություն միասնական

.սինտաքս [միասնական | բաժանված]

Այս հրահանգը սահմանում է Հրահանգների հավաքածուի շարահյուսությունը, ինչպես նկարագրված է ARM-Instruction-Set բաժնում

9.4.2.1 Հրահանգների շարք շարահյուսություն Երկու փոքր -ինչ տարբեր շարահյուսություններ աջակցում են ARM և THUMB հրահանգներին: Լռելյայն, բաժանված, օգտագործում է հին ոճը, որտեղ ARM և THUMB հրահանգներն ունեին իրենց առանձին, առանձին շարահյուսությունները: Նոր, միասնական շարահյուսություն, որը կարող է ընտրվել.syntax հրահանգի միջոցով:

.fpu vfpv4

GCC- ի կոմպիլյատորը կարող է արտադրել երկուական ՝ լողացող կետի վերաբերյալ մի քանի տարբերակով. Փափուկ - հարմար է պրոցեսորների վրա առանց FPU- ի հաշվարկների. Հաշվարկները կատարվում են ծրագրակազմի կողմից `կազմողի կողմից արտադրված softfp - հարմար է պրոցեսորի վրա FPU- ով կամ առանց դրա գործարկելու համար, եթե առկա է, կօգտագործի FPU:. Մեր կոնկրետ գործի համար (դուք պետք է կատարեք ձեր սեփական հետազոտությունը), այս կոնկրետ խորհրդի FPU- ն համապատասխանում է vfpv4- ին: Հնարավոր է ՝ ստիպված լինես խաղալ սրա հետ: Կամ նույնիսկ թողեք այն softfp- ում:

. thumb (vs.arm)

Այս ARM միկրոկառավարիչն իրականում ունի հրահանգների հավաքածուի խառնուրդ: Մեկը ԲԱՆԿ է, մյուսը `ԲԱUM: Մեկ տարբերությունը 16-բիթ հրահանգներն են ՝ 32-բիթ հրահանգների համեմատ: Այսպիսով, այս հրահանգը կազմողին ասում է, որ հետագա հրահանգներին վերաբերվի որպես THUMB կամ ARM:

Մենք պարզապես կվերցնենք ֆայլի մնացորդը այնպես, ինչպես կա, քանի որ այս Instructables- ը դեռ չի խորացել ընդհատումների վրա հիմնված հավաքման ծրագրավորման մեջ:

Քայլ 7. «Բարև աշխարհ» ծրագրի համաժողովի տարբերակը

Հետևյալը կարող է մտնել նաև նախկինում ստեղծված «core. S» ֆայլ: Սա, կրկին, հոդվածի օրինակից է:

/ * * Վերակայման կարգավորիչը: Calանգված է վերակայման ժամանակ: */.type reset_handler, %function reset_handler: // Տեղադրեք կույտի ցուցիչը դեպի կույտի վերջը: // «_estack» արժեքը սահմանվում է մեր կապող սցենարում: LDR r0, = _estack MOV sp, r0

// Սահմանեք որոշ կեղծ արժեքներ: Երբ մենք տեսնում ենք այս արժեքները

// մեր վրիպազերծիչում մենք կիմանանք, որ մեր ծրագիրը // բեռնված է չիպի վրա և աշխատում է: LDR r7, = 0xDEADBEEF MOVS r0, #0 main_loop: // Ավելացնել 1 ՝ գրանցելու համար 'r0': ADDS r0, r0, #1 // Հետադարձ օղակ: B main_loop.size reset_handler,.-Reset_handler

Այսպիսով, վերը նշված ծրագրի հիմնական նպատակն է ճանաչելի օրինաչափություն բեռնել մեկ հիմնական MCU գրանցամատյանում (այս դեպքում ՝ R7), և զրոյից սկսվող աճող արժեք ՝ մեկ այլ հիմնական MCU գրանցամատյանում (այս դեպքում ՝ R0): Եթե մենք անցնում ենք կատարման կոդի միջով, մենք պետք է տեսնենք R0- ի տվյալների ավելացումը:

Եթե դուք հետևում եք MSP432- ի և TI-RSLK դասընթացների/լաբորատորիաների վերաբերյալ Հրահանգներին, ապա վերը նշված բոլոր ծրագրերը պետք է ձեզ ծանոթ լինեն:

R7 գրանցման համար «DEADBEEF» - ը բեռնելիս «=» - ի օգտագործումը կարող է լինել միայն մեկ նոր բան: Մենք դա չէինք օգտագործել:

Այստեղ կցված «core. S» ֆայլը այժմ պարունակում է ամբողջական աղբյուրը:

Քայլ 8: Կոդի կազմում

It'sամանակն է անել հրամանի տողում ինչ-որ բաներ: Ինչ -որ իրական բան, վերջապես:

Այնուամենայնիվ, մենք այնքան էլ այնտեղ չենք: Մենք կրկին ստիպված ենք փոփոխել հոդվածում տրված հրամանը և փոփոխել այն մեր սեփական իրավիճակին:

Ահա օրինակի կոդը.

arm -none -eabi -gcc -x assembler -with -cpp -c -O0 -mcpu = cortex -m0 -mthumb -Wall core. S -o core.o

Եթե մենք գնանք gnu.org կայք GCC- ի համար, (այս դեպքում ՝ տարբերակ 7.3),

x

-X- ը պետք է նշի լեզուն: Հակառակ դեպքում, եթե ոչ -x, ապա կոմպիլյատորը կփորձի գուշակել ՝ օգտագործելով ֆայլի ընդլայնումը: (մեր դեպքում ՝ *. S):

Հոդվածից բերված վերը նշված օրինակը սահմանում է assembler-with-cpp, բայց մենք կարող էինք պարզապես հավաքող անել:

գ

-C- ն ասում է «կազմեք, բայց մի կապեք:

0

-O- ն պետք է սահմանի օպտիմալացման մակարդակը: -O0 (oh -zero) -ի օգտագործմամբ ասվում է "կրճատել կազմման ժամանակը և ստիպել կարգաբերում տալ ակնկալվող արդյունքները: Սա կանխադրված է":

mcpu = ծառի կեղեվ-m0

-Mcpu- ն նշում է նպատակային պրոցեսորի անունը: Մեր դեպքում դա կլիներ cortex-m4:

մայրիկ

-Mthumb- ը սահմանում է ARM և THUMB վիճակները կատարող գեներացնող կոդի միջև ընտրություն կատարելը:

Պատ

-Wall- ը, իհարկե, շատ տարածված է և հայտնի: Այն միացնում է բոլոր նախազգուշական դրոշները:

Վերջապես, հրամանի վերջում մենք ունենք մուտքային ֆայլի core. S և ելքային ֆայլը core.o.

Ահա արդյունքում ստացվող նոր հրամանի տողը `համապատասխանելու մեր կոնկրետ գործին:

arm -none -eabi -gcc -x assembler -c -O0 -mcpu = cortex -m4 -mthumb -Wall core. S -o core.o

Եվ դա կազմեց.

Քայլ 9: ingրագրի միացում

Հոդվածի օրինակից անմիջապես մենք ունենք սա.

arm -none -eabi -gcc core.o -mcpu = cortex -m0 -mthumb -Wall --specs = nosys.specs -nostdlib -lgcc -T./STM32F031K6T6.ld -o main.elf

Վերոնշյալներից շատերը դուք տեսել եք: Ստորև ներկայացված է նորությունը:

specs = nosys.specs

Այս մեկը մի փոքր բարդ է բացատրել:

Դա կապված է «կիսահաղորդակցման» և «հետընտրական նպատակների» հետ, և դա կապված է մուտքի / ելքի հետ: Այն նաև կապված է համակարգային զանգերի և գրադարանների հետ:

Սովորաբար, ներդրված համակարգերը չեն ապահովում ստանդարտ մուտքային/ելքային սարքեր: Սա կազդի համակարգային կամ գրադարանային զանգերի վրա (օրինակ ՝ printf ()):

Semihosting- ը նշանակում է, որ վրիպազերծիչը (տե՛ս Քայլ 11 -ի պատկերը ՝ կարմիր գույնով շրջապատված վրիպազերծիչի հատվածով) ունի հատուկ ալիք և օգտագործում է կիսահաղորդման արձանագրությունը, և դուք կարող եք տեսնել printf () ելքը հյուրընկալող մեքենայի վրա (վրիպազերծիչի միջոցով):

Մյուս կողմից, հետադարձ նպատակ դնելը նշանակում է, որ այդ նույն համակարգային կամ գրադարանային զանգերն այլ բան են նշանակում: Նրանք այլ բան են անում, որն իմաստ ունի ներդրված համակարգի համար: Ինչ -որ իմաստով, ասենք printf () - ի համար, կա նոր իրականացում, այդ գործառույթի հետադարձ նպատակային կիրառում:

Այդ ամենն ասելով ՝ --specs = nosys.specs- ը նշանակում է, որ մենք կիսահաղորդավար չենք լինելու: Սովորաբար դա կնշանակի, որ մենք կրկին թիրախավորում ենք: Դա մեզ բերում է հաջորդ դրոշին:

nostdlib

Կապող տարբերակը -nostdlib օգտագործվում է ինքնուրույն գործարկելու համար նախատեսված ծրագիր կապելու համար: -nostdlib- ը ենթադրում է անհատական ընտրանքներ -nodefaultlibs և -nostartfiles: Ստորև մենք առանձին քննարկում ենք երկու տարբերակները, բայց ամենատիպիկ օգտագործումը պարզապես nostdlib- ն է մեկանգամյա գնումների համար: Հյուրընկալվող ծրագիրը կապելիս ստանդարտ համակարգային գրադարանները, ինչպիսիք են libc- ը, լռելյայնորեն կապված են `ծրագրին հասանելի դարձնելով բոլոր ստանդարտ գործառույթները (printf, Ստրլեն և ընկերներ): The linker տարբերակը -nodefaultlibs անջատում է կապը այդ կանխադրված գրադարանների հետ. միակ գրադարաններն այն գրադարաններն են, որոնք դուք հստակորեն անվանում եք կապողին -l դրոշի միջոցով:

lgcc

libgcc.a- ն ստանդարտ գրադարան է, որն ապահովում է ներքին ենթածրագրեր `որոշակի մեքենաների թերությունները հաղթահարելու համար: Օրինակ, ARM պրոցեսորը չի ներառում բաժանման հրահանգ: Libgcc.a- ի ARM տարբերակը ներառում է բաժանման գործառույթ, և կոմպիլյատորը անհրաժեշտության դեպքում զանգեր է արձակում այդ գործառույթին:

Տ

Սա պարզապես միջոց է ասելու, որ կապողն օգտագործի այս ֆայլը որպես կապող սցենար: Մեր դեպքում ֆայլի անունը linker.script.ld է:

հիմնական: ինքս

Ի վերջո, մենք կապողին ասում ենք, թե ինչ է լինելու վերջնական ելքային պատկերի ֆայլի անունը, որը կայրվի/կպայթեցվի մեր սարքի մեջ:

Ահա հրամանի տողի ամբողջական տարբերակը ՝ փոփոխված մեր հատուկ իրավիճակի համար.

arm -none -eabi -gcc core.o -mcpu = cortex -m4 -mthumb -Wall --specs = nosys.specs -nostdlib -lgcc -T./linker.script.ld -o main.elf

Մենք համոզվում ենք, որ սցենարային ֆայլը և core.o ֆայլը երկուսն էլ նույն գրացուցակում են, որտեղ մենք գործարկելու ենք վերը նշված հրամանի տողը:

Եվ դա կապում է առանց խնդիրների:

Չեկ

Այնուհետև մենք վազում ենք.

arm-none-eabi-nm main.elf

և մենք ստանում ենք.

devchu@chubox: ~/Development/Atollic/TrueSTUDIO/STM32_workspace_9.1 $ arm-none-eabi-nm main.elf 20014000 A _estack 08000010 t main_loop 08000008 T reset_handler 08000000 T vtable

Լավ է նայվում. Arm-none-eabi-nm հրամանը օբյեկտային ֆայլերի մեջ խորհրդանիշներ թվարկելու միջոց է:

Քայլ 10. Փորձարկում Միացում STM32 Nucleo-64- ին

TestingConction to STM32 Nucleo-64
TestingConction to STM32 Nucleo-64
TestingConction to STM32 Nucleo-64
TestingConction to STM32 Nucleo-64

Եթե դուք ընդունեք դա, ձեր առաջին առաքելությունն է հասնել ձեր համակարգին տեսնել ձեր զարգացման խորհուրդը:

Օգտագործելով Windows

Windows- ի համար ես որոշեցի տեղադրել TrueSTUDIO- ն Atollic- ից (անվճար տարբերակ): Դա ցավոտ տեղադրում էր և ինքնաբերաբար տեղադրեց վարորդը, որպեսզի ես կարողանամ օգտագործել st-link կապը փորձարկելու համար: Երբ ես տեղադրել եմ TrueSTUDIO- ն և սարքի կառավարիչը տեսել է սարքը, ես ներբեռնել եմ տեքստային/ստլինկ գործիքները, որոնք առաջարկվել են մերկ մետաղների հոդվածով, որին մենք հետևում էինք: Ես նորից թղթապանակը տեղադրեցի անմիջապես «C: \» - ի տակ և նորից որոշ հղումներ ստեղծեցի իմ տեղական cygwin տնակի աղբարկղից դեպի հրամանները:

ln -s /c/STM32. MCU/stlink-1.3.0-win64/bin/st-info.exe ~/bin/st-info

Որպես նախնական փորձություն ՝ տեսնելու, թե արդյոք մենք իսկապես կարող ենք շփվել սարքի հետ, ես վազեցի.

st-info-հետաքննություն

Եվ վերադարձավ.

Գտնվել է 1 ստիլ ծրագրավորող

Այսպիսով, այժմ մենք գիտենք, որ կարող ենք զրուցել/հարցնել մեր զարգացման խորհուրդը:

Linux- ի օգտագործումը

Linux- ի համար վարորդ իսկապես պետք չէ: Բայց Debian- ի համար դուք պետք է կառուցեք st գործիքները աղբյուրից:

git կլոն

Համոզվեք, որ ունեք libusb-1.0-0-dev- ը:

համապատասխան ցուցակ | grep -E "*libusb.*dev*"

Դուք պետք է տեսնեք.

libusb-1.0-0-dev/xenial, այժմ 2: 1.0.20-1 amd64 [տեղադրված է]

կամ նման մի բան:

Տեղադրելու համար.

sudo apt-get տեղադրել libusb-1.0-0-dev

Նշենք, որ վերը նշվածը նույնը չէ, ինչ.

sudo apt-get տեղադրել libusb-dev

Missingիշտ բացակայող libusb dev- ը կարող է առաջացնել cmake- ի խնդիրներ:

CMake Error: Այս նախագծում օգտագործվում են հետևյալ փոփոխականները, բայց դրանք դրված են NOTFOUND. Խնդրում ենք սահմանել դրանք կամ համոզվել, որ դրանք CMake ֆայլերում ճիշտ դրված և փորձարկված են. LIBUSB_INCLUDE_DIR (ADVANCED)

Փոխեք նախագծի արմատային գրացուցակը (… blah /blah /stlink): Կատարեք «արձակուրդ»:

Կառուցումից հետո գործիքները պետք է լինեն «.. /build /Release» բաժնում:

Այնուհետև կարող եք գործարկել «st-info -probe»: Ահա ելքը միացված Nucleo- ով, ապա ոչ:

devchu@chubox: ~/Development/stlink $./build/Release/st-info --probe Գտնվել է 1 stlink ծրագրավորողի սերիա ՝ 303636414646353034393535363537 openocd: "\ x30 / x36 / x36 / x41 / x46 / x46 / x35 / x30 / x30 / x34 / x39 / x35 / x35 / x36 / x35 / x37 "ֆլեշ: 524288 (էջի չափը` 2048) sram: 65536 չիպ. info -հետաքննություն Գտնվել է 0 stlink ծրագրավորող devchu@chubox: ~//արգացում/stlink $

Քայլ 11: Եկեք օգտագործենք GDB- ը Linux- ով

Եկեք օգտագործենք GDB Linux- ով
Եկեք օգտագործենք GDB Linux- ով
Եկեք օգտագործենք GDB Linux- ով
Եկեք օգտագործենք GDB Linux- ով

Եթե դուք փորձել եք այս ամենը, և հասել եք այսքան հեռու, հիանալի: Գերազանց Եկեք հիմա մի փոքր զվարճանանք:

Երբ գնում եք ARM- ի զարգացման այս տախտակները, լինեն դրանք Texas Instruments- ի MSP432 Launchpad- ը, թե այս մեկը, որը մենք այժմ քննարկում ենք, Nucleo-F303 (STM32 Nucleo-64), դրանք սովորաբար ժամանում են արդեն շողշողացող գործարկվող ծրագրով: որոշ թարթող ծրագիր, որը ներառում է նաև անջատիչին սեղմելը ՝ LED (եր) -ի բռնկման արագությունը փոխելու համար:

Նախքան այդքան արագ գրել-կարդալը, եկեք տեսնենք, թե ինչ կա տեսնելու և անելու:

Linux- ի միջոցով բացեք տերմինալ, փոխեք գրացուցակը, որը մենք կառուցել ենք, և գտեք st-util գործիքը:

devchu@chubox: ~/Development/stlink $ find. -անուն st-util

./build/Release/src/gdbserver/st-util

Գործարկեք այդ գործիքը: Քանի որ մենք արդեն իսկ փորձարկել ենք մեր կապը st-info-probes- ի հետ, մենք պետք է ստանանք այնպիսի ելք, ինչպիսին է `

devchu@chubox: ~/Development/stlink $./build/Release/src/gdbserver/st-util

st-util 1.4.0-50-g7fafee2 2018-10-20T18: 33: 23 ՏԵFԵԿՈԹՅՈՆ common.c: Սարքի պարամետրերի բեռնում…. 2018-10-20T18: 33: 23 INFO common.c: Սարքը միացված է ՝ F303 բարձր խտության սարք, id 0x10036446 2018-10-20T18: 33: 23 INFO common.c: SRAM չափ ՝ 0x10000 բայթ (64 ԿԲ), Flash ՝ 0x80000 բայթ (512 KiB) 2048 բայթերի էջերում 2018-10-20T18: 33: 23 INFO gdb-server.c: Չիպի ID- ն 00000446 է, հիմնական ID- ն ՝ 2ba01477: 2018-10-20T18: 33: 23 INFO gdb-server.c: Լսելով *: 4242…

Դա GDB սերվերն է, որն այժմ աշխատում է, և այն տեսնում է մեր զարգացման տախտակը, և որ ավելի կարևոր է, այն լսում է 4242 նավահանգստում (կանխադրված նավահանգիստ):

Այժմ մենք պատրաստ ենք աշխատանքից ազատել GDB հաճախորդին:

Linux- ում բացեք մեկ այլ տերմինալ, մուտքագրեք սա.

arm-none-eabi-gdb -tui

Դա նույնն է, ինչ gdb- ի խիստ հրամանի տողը գործարկելը, սակայն դրա փոխարեն արտադրում է տեքստային տերմինալ (ենթադրությամբ `այն օգտագործում է հայհոյանքներ):

Մենք ունենք GDB հաճախորդ և GDB սերվեր: Այնուամենայնիվ, հաճախորդը միացված չէ սերվերին: Այս պահին այն ոչինչ չգիտի մեր Nucleo- ի (կամ ձեր ընտրած խորհրդի) մասին: Մենք պետք է դա ասենք: Տերմինալում ձեր հուշումը այժմ պետք է լինի «(gdb)»: Մուտքագրեք ՝

օգնության թիրախ

Այն կտա ձեզ ցուցակ: Ուշադրություն դարձրեք, որ այն, ինչ մենք ուզում ենք, թիրախ է `ընդլայնված -հեռավոր. Օգտագործեք հեռակա համակարգիչ սերիական գծի միջոցով:

Բայց մենք նաև պետք է տրամադրենք դրա գտնվելու վայրը: Այսպիսով, (gdb) հուշման մեջ մուտքագրեք.

(gdb) թիրախ ընդլայնված-հեռավոր localhost: 4242

Դուք պետք է պատասխան ստանաք հետևյալ կերպ.

(gdb) թիրախ ընդլայնված-հեռավոր localhost: 4242

Հեռակա կարգաբերում ՝ օգտագործելով localhost ՝ 4242 0x080028e4 դյույմ ?? ()

Մինչդեռ, st-util gdbserver- ով աշխատող տերմինալում մենք ստացանք սա.

2018-10-20T18: 42: 30 ՏԵFԵԿԱՏՎՈԹՅՈՆ gdb-server.c: Գտնվել է 6 hw ընդմիջման գրանցամատյան

2018-10-20T18: 42: 30 INFO gdb-server.c: GDB միացված է:

Քայլ 12: Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով

Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով
Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով
Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով
Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով
Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով
Եկեք կրկնենք ՝ Windows- ով և Flash մեր ծրագրով

St-util gdbserver- ի և arm-none-eabi-gdb հաճախորդի գործարկման քայլերն ըստ էության նույնն են, ինչ մենք անում էինք նախորդ Քայլի ընթացքում: Դուք բացում եք երկու տերմինալ (cygwin, DOS cmd կամ Windows Powershell), գտնում եք st-util- ի գտնվելու վայրը և գործարկում այն: Մյուս տերմինալում գործարկեք arm-none-eabi-gdb հաճախորդը: Միակ տարբերությունն այն է, որ -tui (տերմինալների վրա հիմնված տեքստի դիտում) ռեժիմը, ամենայն հավանականությամբ, չի ապահովվում:

Եթե վերը նշվածն աշխատում էր Windows- ում, ապա, ամենայն հավանականությամբ, ստիպված կլինեք կանգ առնել (պարզապես հաճախորդը): Այս պահին ինչ -որ կերպ ձեզ հարկավոր կլինի գործարկել GDB հաճախորդը, որտեղ գտնվում է ձեր կառուցման ֆայլը ("core.out"), կամ ավելացնել այդ ֆայլին ամբողջ ուղին ՝ որպես փաստարկ GDB հաճախորդին:

Ես պարզեցրեցի իմ կյանքը ՝ օգտագործելով cygwin և ստեղծելով հղումներ իմ տեղական $ HOME // bin գրացուցակից, որտեղ գտնվում են այդ երկու գործիքները:

Լավ, մենք հավաքել և կապել ենք նախկինի պես, և մենք ունենք main.elf ֆայլը, որը պատրաստ է բռնկվելու:

Մենք ունենք մեկ պատուհանում աշխատող st-util. Մենք վերագործարկում ենք GDB հաճախորդը, այս անգամ անում ենք.

arm-none-eabi-gdb main.elf

Մենք թույլ ենք տալիս այն սկսել, սպասել (gdb) հուշմանը, կատարել մեր միացման նույն հրամանը GDB սերվերին (st-util) և մենք պատրաստ ենք բռնկել գործարկվողը: Շատ հակակլիմայական է.

(gdb) բեռ

Cygwin տերմինալների հետ աշխատելու դեպքում հայտնի խնդիր կա, երբ վահանակի հրամանները չեն թողարկվում: Այսպիսով, մեր դեպքում սերվերը գործարկող պատուհանը լիովին լուռ էր: Այն, ով աշխատում է հաճախորդին, որտեղ մենք վարում էինք բեռը, թողարկում է սա.

Բեռնման բաժին:

Քայլ 13. Linuxրամեկուսացում Linux- ով - Ավելի շատ պարգևատրում. D

Flashրամեկուսացում Linux- ով - Ավելի շատ պարգևատրում: D
Flashրամեկուսացում Linux- ով - Ավելի շատ պարգևատրում: D

Քայլ 14. Եկեք սուզվենք մի փոքր ավելի խորը

Եթե հասել եք այստեղ, հիանալի: Անցնենք առաջ:

Ինչու՞ չնայել main.elf ֆայլի ներսում `գործարկելի: Գործարկեք հետևյալը.

arm-none-eabi-objdump -d main.elf

Դուք պետք է տեսնեք այսպիսի մի բան.

main.elf: ֆայլի ձևաչափ elf32-littlearm

Հատվածի ապամոնտաժում.text.

08000000:

8000000: 00 40 01 20 09 00 00 08.@. ….

08000008:

8000008: 4802 ldr r0, [հատ, #8]; (8000014) 800000a: 4685 mov sp, r0 800000c: 4f02 ldr r7, [հատ, #8]; (8000018) 800000e: 2000 movs r0, #0

08000010:

8000010: 3001 ավելացնում է r0, #1 8000012: e7fd b.n 8000010 8000014: 20014000.բառ 0x20014000 8000018: deadbeef.բառ 0xdeadbeef

Ինչ փոքրիկ բեկորներ կարող ենք ստանալ վերը նշված արտադրանքից:

Եթե հիշում եք, երբ քննարկում և ստեղծում էինք linker.script.ld ֆայլը, մենք նշեցինք, որ այս ARM սարքերն ունեն RAM ՝ սկսած 0x20000000, և որ FLASH հիշողությունը սկսվում է 0x08000000 -ից:

Այսպիսով, մենք կարող ենք տեսնել, որ իսկապես ծրագիրն այնպիսին է, որ այն բոլորը գտնվում են FLASH հիշողության մեջ:

Այնուհետև, վերևում, բայց ավելի ուշ քայլում, երբ մենք քննարկում էինք «Բարև աշխարհ» հատվածը, կար մի հայտարարություն, որտեղ մենք բեռնում ենք անմիջական, հաստատուն, բառացի արժեքը («0xDEADBEEF») MCU հիմնական գրանցամատյանում («R7»):

Հայտարարությունը հետևյալն էր.

LDR R7, = 0xDEADBEEF

Մեր ծածկագրում դա միակ վայրն է, որտեղ մենք նույնիսկ նշում ենք DEADBEEF- ը: Ուրիշ ոչ մի տեղ: Եվ, այնուամենայնիվ, եթե նայեք վերը բերված ապամոնտաժված/վերակառուցված հրահանգներին և այլն, ապա DEADBEEF- ի հետ կապված ավելի շատ բան կա, քան մենք կարծում էինք:

Այսպիսով, կազմողը/կապողը ինչ -որ կերպ որոշեց մշտապես փչացնել DEADBEEF- ի արժեքը FLASH հասցեով ՝ 0x8000018 վայրում: Եվ հետո, կազմողը փոխեց մեր վերը նշված LDR հրահանգը ՝

LDR R7, [ԱՀ, #8]

Դա նույնիսկ մեկնաբանություն առաջացրեց մեզ համար: Ինչքան գեղեցիկ. Եվ դա մեզ ասում է, որ վերցնենք ծրագրի ներկայիս հաշվիչի արժեքը (համակարգչի գրանցամատյանը), այդ արժեքին ավելացնենք 0x8, և հենց այնտեղ է այրվել DEADBEEF- ը, և ստացիր այդ արժեքը և լցրու R7- ում:

Այսպիսով, դա նաև նշանակում է, որ ծրագրի հաշվիչը (ԱՀ) մատնանշում էր 0x8000010 հասցեն, որը հիմնական_հանգույցի սկիզբն է, և որ DEADBEEF արժեքը հիմնական հասցեագրման ավարտից հետո գտնվում է երկու հասցեում:

Քայլ 15: Վերջապես, համառոտ դիտարկում ծրագրի ընթացքի վերաբերյալ

Նույնիսկ եթե դուրս գաք GDB- ից, պարզապես նորից մուտքագրեք հրամանը: Դուք նույնիսկ կարիք չունեք դրան որևէ ֆայլ տալու. մենք այլևս չենք թարթում, պարզապես այն գործարկում ենք:

GDB հաճախորդը նորից միացնելով GDB սերվերին ՝ հրամանի տողում (gdb).

(gdb) տեղեկատվության գրանցամատյաններ

Դուք պետք է տեսնեք այսպիսի բան.

r0 0x0 0

r1 0x0 0 r2 0x0 0 r3 0x0 0 r4 0x0 0 r5 0x0 0 r6 0x0 0 r7 0x0 0 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0x0 0 r12 0x0 0 sp 0x20014000 0x20014000 lr 0xfffff8000000000000000000000000000000000000000000000000000000000000000000000

Բայց հետո, (gdb) հուշման մեջ մուտքագրեք.

(gdb) շարունակել

Եվ շատ արագ հարվածեց CTRL-C- ին: Դա պետք է դադար տա ծրագիրը: Կրկին մուտքագրեք «տեղեկատվական գրանցամատյաններ» հրամանը:

Այս անգամ այն այլ տեսք ունի.

(gdb) տեղեկատվության գրանցամատյաններ

r0 0x350ffa 3477498 r1 0x0 0 r2 0x0 0 r3 0x0 0 r4 0x0 0 r5 0x0 0 r6 0x0 0 r7 0xdeadbeef 3735928559 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0x0 0 rff 0x00 0000000000000000000000000000000000000000000000000000000000000000000000000000000 16777216

Ինչ է պատահել? Հենց այն, ինչ մենք էինք ուզում: DEADBEEF- ը բեռնված է R7- ում, իսկ R0- ը (չափազանց արագ) աճում է: Եթե կրկնում եք, նորից կտեսնեք R0- ն այլ արժեքով:

Քայլ 16. Մենք ցանկանում էինք Flash- ում ստեղծել միայն կարդալու զանգված

Assemblyանգվածի համարժեք ստեղծման եղանակներից մեկը `օգտագործելով հավաքումը և հրահանգները, հետևյալն է.

.տիպ myarray, %օբյեկտ // անունը կամ պիտակը 'myarray' սահմանվում է որպես օբյեկտի տեսակ:

myarray: // սա սկիզբն է «myarray» հռչակագրի // (ինչից այն բաղկացած կլինի):.word 0x11111111 // «myarray» - ում պարունակվող առաջին անդամը կամ արժեքը:.word 0x22222222 // երկրորդ արժեքը (հարակից հասցեներ):.բառ 0x33333333 // և այլն:.sar myarray, Այժմ, երբ մենք այն տեղադրել ենք FLASH հիշողության մեջ, կարող ենք այն օգտագործել ծրագրում: Ստորև ներկայացնում ենք հատված.

LDR R1, myarray // սա բեռնում է «myarray» - ի 1 -ին վայրում պարունակվող տվյալները »: // սա այն չէ, ինչ մենք ուզում ենք:

LDR R1, = myarray // սա ինքն է բեռնում տեղադրության արժեքը (1 -ին հասցեն), // ոչ տվյալները.. // սա այն է, ինչ մենք ուզում ենք:

MOV R2, #0 // R2- ը հաշվառում կպահի `համոզվելու համար, որ մենք չենք հեռանում

// զանգվածի վերջ: LDR R3, = myarrsize // R3 կլինի «myarrsize» - ի համարժեքը:

// R0- ը կպահի մեր տվյալները

main_loop:

LDR R0, [R1] // Տեղադրեք R1 («myarray») մատնանշված տվյալները R0- ում: CMP R2, R3 // Արդյո՞ք մենք զանգվածի սահմաններում ենք: BEQ main_loop // Եթե մենք ավարտենք, մենք ավարտեցինք, այնպես որ մենք պարզապես կշրջվենք ընդմիշտ:

ADD R2, #1 // Հակառակ դեպքում, մենք կարող ենք անընդհատ կրկնել զանգվածի միջոցով:

ADD R1, #4 // R1 գրանցելու համար ավելացրեք 4, այնպես որ այն ճիշտ է ցույց տալիս հաջորդին

// հասցե..

B main_loop // Հետադարձ օղակ:

Տեսանյութը անցնում է այս ամենի միջով, և դրա մեջ կա սխալ: Դա լավ է; դա ցույց է տալիս, որ դա կարևոր գործարկման և կարգաբերման կոդ է: Այն ցույց է տալիս զանգվածի ծայրից դուրս գալու դասական դեպք:

Խորհուրդ ենք տալիս: