ماشین حالت یک مدل انتزاعی است که شامل تعداد محدودی از حالات چیزی است. برای نمایش و کنترل جریان اجرای هر دستور استفاده می شود. ماشین حالت ایده آل برای پیاده سازی هوش مصنوعی در بازی ها، به دست آوردن یک راه حل منظم بدون نوشتن کدهای دست و پا گیر و پیچیده است. در این مقاله، تئوری را پوشش خواهیم داد و همچنین نحوه استفاده از یک ماشین حالت ساده و مبتنی بر پشته را یاد خواهیم گرفت.

ما قبلاً مجموعه ای از مقالات در زمینه نوشتن هوش مصنوعی با استفاده از ماشین حالت منتشر کرده ایم. اگر هنوز این مجموعه را نخوانده اید، می توانید همین الان این کار را انجام دهید:

ماشین حالت محدود چیست؟

ماشین حالت محدود (یا به سادگی FSM - ماشین حالت محدود) یک مدل محاسباتی مبتنی بر یک ماشین حالت فرضی است. فقط یک ایالت می تواند در هر زمان فعال باشد. بنابراین برای انجام هر عملی، ماشین باید حالت خود را تغییر دهد.

معمولاً از ماشین های حالت برای سازماندهی و نمایش جریان اجرای چیزی استفاده می شود. این به ویژه هنگام پیاده سازی هوش مصنوعی در بازی ها مفید است. به عنوان مثال، برای نوشتن "مغز" دشمن: هر دولت نشان دهنده نوعی عمل (حمله، طفره رفتن و غیره) است.

یک خودکار محدود را می توان به عنوان یک نمودار نشان داد که رئوس آن حالت ها و یال ها انتقال بین آنها هستند. هر لبه دارای یک برچسب است که نشان می دهد چه زمانی انتقال باید رخ دهد. به عنوان مثال، در تصویر بالا می بینید که خودکار حالت را از "سرگردان" به حالت "حمله" تغییر می دهد، البته به شرطی که بازیکن در نزدیکی خود باشد.

زمان بندی حالت ها و انتقال آنها

پیاده سازی یک ماشین حالت محدود با شناسایی حالات آن و انتقال بین آنها آغاز می شود. ماشین حالتی را تصور کنید که عملکرد مورچه ای را که برگ ها را به داخل یک مورچه حمل می کند، توصیف می کند:

نقطه شروع حالت "برگ را پیدا کن" است که تا زمانی که مورچه برگ را پیدا کند فعال باقی می ماند. هنگامی که این اتفاق می افتد، وضعیت به "برو به خانه" تغییر می کند. همین حالت تا زمانی که مورچه ما به لانه مورچه نرسد فعال باقی خواهد ماند. پس از آن، حالت به "یافتن برگ" تغییر می کند.

اگر حالت "پیدا کردن برگ" فعال باشد، اما نشانگر ماوس در کنار مورچه باشد، وضعیت به "فرار کردن" تغییر می کند. به محض اینکه مورچه در فاصله کافی ایمن از مکان نما ماوس قرار گرفت، وضعیت دوباره به "یافتن برگ" تغییر می کند.

لطفاً توجه داشته باشید که هنگام رفتن به خانه یا بیرون از خانه، مورچه از نشانگر ماوس نمی ترسد. چرا؟ و چون هیچ انتقال متناظری وجود ندارد.

پیاده سازی یک ماشین حالت ساده

یک ماشین حالت را می توان با استفاده از یک کلاس واحد پیاده سازی کرد. بیایید آن را FSM بنامیم. ایده این است که هر حالت را به عنوان یک روش یا تابع پیاده سازی کنیم. همچنین از ویژگی activeState برای تعیین وضعیت فعال استفاده خواهیم کرد.

کلاس عمومی FSM (var private activeState:function; // اشاره گر به وضعیت فعال تابع عمومی خودکار FSM() () تابع عمومی setState(state:Function) :void (activState = state;) به روز رسانی عملکرد عمومی() :void (اگر (activState!= null) (activState();))

هر حالت یک تابع است. علاوه بر این، به گونه ای که هر بار که فریم بازی به روز می شود، فراخوانی می شود. همانطور که قبلا ذکر شد، activeState یک اشاره گر به تابع حالت فعال ذخیره می کند.

متد update() کلاس FSM باید هر فریم بازی نامیده شود. و او نیز به نوبه خود عملکرد آن حالت را که در این لحظهفعال است.

متد setState() وضعیت فعال جدید را تنظیم می کند. علاوه بر این، هر تابعی که وضعیتی از خودکار را تعریف می کند، لازم نیست به کلاس FSM تعلق داشته باشد - این باعث می شود کلاس ما جهانی تر شود.

با استفاده از ماشین دولتی

بیایید هوش مصنوعی ant را پیاده سازی کنیم. در بالا، ما قبلا مجموعه ای از حالات و انتقال بین آنها را نشان داده ایم. بیایید دوباره آنها را توضیح دهیم، اما این بار روی کد تمرکز می کنیم.

مورچه ما با کلاس Ant نشان داده می شود که دارای یک میدان مغزی است. این فقط یک نمونه از کلاس FSM است.

کلاس عمومی Ant (موقعیت var عمومی:Vector3D؛ سرعت var عمومی:Vector3D؛ مغز var عمومی:FSM؛ عملکرد عمومی Ant(posX:Number، posY:Number) ( position = new Vector3D(posX, posY); velocity = new Vector3D( -1، -1؛ brain = new FSM(); // با جستجوی برگ شروع کنید. brain. setState(findLeaf); ) /** * حالت "findLeaf". * باعث می شود مورچه به دنبال برگ بگردد. */ تابع عمومی findLeaf( :void () /** * حالت "goHome" * باعث می شود مورچه به داخل مورچه برود */ تابع عمومی goHome() :void () /** * حالت "runAway" * باعث می شود ant برای فرار از نشانگر ماوس * / تابع عمومی runAway() :void () عملکرد عمومی به روز رسانی(): void ( // به روز رسانی ماشین حالت. این تابع // تابع حالت فعال را فراخوانی می کند: findLeaf(), goHome () یا runAway(). brain.update(); // اعمال سرعت به حرکت مورچه. moveBasedOnVelocity(); ) (...) )

کلاس Ant همچنین دارای ویژگی های سرعت و موقعیت است. این متغیرها برای محاسبه حرکت با استفاده از روش اویلر استفاده خواهند شد. تابع update() هر بار که فریم بازی به روز می شود فراخوانی می شود.

در زیر پیاده سازی هر یک از متدها، با findLeaf()، حالتی که مسئول یافتن برگ ها است، شروع می شود.

تابع عمومی findLeaf() :void ( // مورچه را به برگ منتقل می کند. velocity = new Vector3D(Game.instance.leaf.x - position.x، Game.instance.leaf.y - position.y)؛ if (فاصله (بازی .instance.leaf، این)<= 10) { // Муравей только что подобрал листок, время // возвращаться домой! brain.setState(goHome); } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши находится рядом. Бежим! // Меняем состояние автомата на runAway() brain.setState(runAway); } }

حالت goHome() برای وادار کردن مورچه به خانه استفاده می شود.

تابع عمومی goHome() :void ( // مورچه را به سرعت اصلی منتقل می کند = new Vector3D(Game.instance.home.x - position.x، Game.instance.home.y - position.y)؛ if (distance( بازی. instance.home، این)<= 10) { // Муравей уже дома. Пора искать новый лист. brain.setState(findLeaf); } }

و در نهایت، حالت runAway () - برای جاخالی دادن نشانگر ماوس استفاده می شود.

تابع عمومی runAway():void ( // مورچه را از سرعت مکان نما دور می کند = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // آیا مکان نما هنوز در اطراف است ? if ( distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) (// نه، از قبل دور است. وقت آن است که به یافتن برگ ها برگردیم. brain.setState(findLeaf); ) )

بهبود FSM: خودکار مبتنی بر پشته

تصور کنید که مورچه در راه خانه نیز باید از نشانگر ماوس فرار کند. حالت های FSM به این صورت است:

به نظر یک تغییر بی اهمیت است. خیر، چنین تغییری برای ما مشکل ایجاد می کند. تصور کنید که وضعیت فعلی "فرار" است. اگر نشانگر ماوس از مورچه دور شود، چه کاری باید انجام دهد: به خانه بروید یا به دنبال برگ بگردید؟

راه حل این مشکل یک ماشین حالت مبتنی بر پشته است. برخلاف FSM ساده ای که در بالا پیاده سازی کردیم، این نوع FSM از یک پشته برای مدیریت حالت ها استفاده می کند. در بالای پشته حالت فعال قرار دارد و زمانی که حالت ها از پشته اضافه یا حذف می شوند، انتقال رخ می دهد.

و در اینجا یک نمایش تصویری از عملکرد یک ماشین حالت بر اساس پشته است:

پیاده سازی FSM مبتنی بر پشته

چنین ماشین حالت محدودی را می توان به همان روشی که یک ماشین ساده پیاده سازی کرد. تفاوت در استفاده از آرایه ای از اشاره گرها به حالت های مورد نیاز خواهد بود. ما دیگر نیازی به ویژگی activeState نخواهیم داشت، زیرا بالای پشته قبلاً به حالت فعال اشاره می کند.

کلاس عمومی StackFSM ( var stack:Array؛ تابع عمومی StackFSM() ( this.stack = new Array() (currentStateFunction(); ) ) تابع عمومی popState() :function ( return stack.pop(); ) تابع عمومی pushState(state:function) :void ( if (getCurrentState() != حالت) (stack.push(state) ; ) ) تابع عمومی getCurrentState(): Function ( بازگشت stack.length > 0 ? stack : null; ) )

توجه داشته باشید که متد setState() با pushState() (افزودن حالت جدید به بالای پشته) و popState() (حذف حالت از بالای پشته) جایگزین شده است.

با استفاده از FSM مبتنی بر پشته

توجه به این نکته مهم است که هنگام استفاده از یک ماشین حالت مبتنی بر پشته، هر حالت مسئول حذف خود از پشته در مواقعی است که نیازی نیست. به عنوان مثال، اگر دشمن قبلاً نابود شده باشد، وضعیت ()attac باید خودش را از پشته حذف کند.

کلاس عمومی Ant ((...) public var brain:StackFSM؛ عملکرد عمومی Ant(posX:Number، posY:Number) ((...) brain = new StackFSM()؛ // با جستجوی مغز برگ شروع کنید. pushState( findLeaf); (...) ) /** * حالت "findLeaf". * باعث می شود مورچه به دنبال برگ بگردد. */ تابع عمومی findLeaf() :void ( // مورچه را به برگ منتقل می کند. سرعت = جدید Vector3D(Game.instance. leaf.x - position.x، Game.instance.leaf.y - position.y); if (distance(Game.instance.leaf, this)<= 10) { //Муравей только что подобрал листок, время // возвращаться домой! brain.popState(); // removes "findLeaf" from the stack. brain.pushState(goHome); // push "goHome" state, making it the active state. } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши рядом. Надо бежать! // Состояние "runAway" добавляется перед "findLeaf", что означает, // что состояние "findLeaf" вновь будет активным при завершении состояния "runAway". brain.pushState(runAway); } } /** * Состояние "goHome". * Заставляет муравья идти в муравейник. */ public function goHome() :void { // Перемещает муравья к дому velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y); if (distance(Game.instance.home, this) <= 10) { // Муравей уже дома. Пора искать новый лист. brain.popState(); // removes "goHome" from the stack. brain.pushState(findLeaf); // push "findLeaf" state, making it the active state } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши рядом. Надо бежать! // Состояние "runAway" добавляется перед "goHome", что означает, // что состояние "goHome" вновь будет активным при завершении состояния "runAway". brain.pushState(runAway); } } /** * Состояние "runAway". * Заставляет муравья убегать от курсора мыши. */ public function runAway() :void { // Перемещает муравья подальше от курсора velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // Курсор все еще рядом? if (distance(Game.mouse, this) >MOUSE_THREAT_RADIUS) ( // نه، از قبل دور است. زمان بازگشت به جستجوی برگ ها است. brain.popState(); ) ) (...) )

نتیجه

ماشین های حالت مطمئنا برای پیاده سازی منطق هوش مصنوعی در بازی ها مفید هستند. آنها را می توان به راحتی به عنوان یک نمودار نشان داد، که به توسعه دهنده اجازه می دهد تا همه گزینه های ممکن را ببیند.

پیاده سازی یک ماشین حالت با توابع حالت یک تکنیک ساده و در عین حال قدرتمند است. حتی درهم تنیدگی های حالت پیچیده تری را می توان با استفاده از FSM پیاده سازی کرد.

یکی از معیارهای پیچیدگی یک خودکار محدود، تعداد حالت های آن است. هر چه این عدد کوچکتر باشد، دستگاه گسسته ای که این خودکار را پیاده سازی می کند ساده تر است. بنابراین یکی از مشکلات مهم در نظریه اتوماتای ​​محدود، ساخت خودکاری با کمترین تعداد حالت است.

از آنجایی که در رایانه های مدرن هر اطلاعاتی به صورت کدهای باینری نمایش داده می شود، پس برای ساخت خودکار می توانید از عناصری استفاده کنید که فقط دو حالت پایدار متفاوت دارند که یکی از آنها با عدد 0 و دیگری با عدد 1 مطابقت دارد.

در اینجا چند نمونه از اتوماتای ​​محدود آورده شده است.

مثال 1. عنصر تاخیر (عنصر حافظه).

عناصر تاخیری دستگاهی با یک ورودی و یک خروجی هستند. علاوه بر این، مقدار سیگنال خروجی در آن زمان تی با مقدار سیگنال در لحظه قبل منطبق است. به طور شماتیک، عنصر تاخیر را می توان به صورت زیر نشان داد (شکل 2).

فرض کنید الفبای ورودی و در نتیجه الفبای خروجی است ایکس ={0, 1}, Y = (0، 1). سپس س = (0، 1). تحت حالت عنصر تاخیر در آن زمان تی محتوای عنصر حافظه در لحظه درک می شود. به این ترتیب q (تی )= ایکس (تی 1)، الف Y (تی )= q (تی )=ایکس (تی 1).

بیایید عنصر تاخیر را با جدول، جایی که آ 1 =0, آ 2 =1, q 1 =0, q 2 =1,

(آ 1 , q 1)= (0, 0)=0, (آ 1 , q 1)= (0, 0)=0;

(آ 1 , q 2)= (0, 1)=0, (آ 1 , q 2)= (0, 1)=1;

(آ 2 , q 1)= (1, 0)=1, (آ 2 , q 1)= (1, 0)=0;

(آ 2 , q 2)= (1, 1)=1, (آ 2 , q 2)= (1, 1)=1;

q

آ

=0, =0

=0, =1

=1, =0

=1, =1

نمودار مور در شکل نشان داده شده است. 3

برای نمایش این خودکار توسط سیستمی از توابع بولی، از جدول خودکار و الگوریتم بالا استفاده می کنیم. در این مورد، رمزگذاری ضروری نیست، زیرا حروف و حالات ورودی و خروجی قبلاً کدگذاری شده اند.

به این نکته توجه کنیم که m=p=p =2. سپس ک =r =س =1، و بنابراین عنصر تاخیر توسط دو تابع داده می شود و . جدول صدق این توابع شامل 2 است ک + r =2 2 =4 ردیف و ک +r +r +س =4 ستون:

ایکس

z

مثال 2. جمع کننده ترتیبی باینری.

این جمع کننده سریال دستگاهی است که دو عدد را در سیستم باینری جمع می کند. اعداد به ورودی های جمع کننده وارد می شوند ایکس 1 و ایکس 2، با کمترین رقم شروع می شود. در خروجی، دنباله ای مطابق با ورودی شماره تشکیل می شود ایکس 1 +ایکس 2 در سیستم دودویی محاسبه (شکل 4).

الفبای ورودی و خروجی به طور منحصر به فرد تعریف می شوند: ایکس ={00; 01; 10; 11}, Y = (0،1). مجموعه ای از حالت ها با مقدار انتقال در هنگام جمع کردن بیت های مربوطه از اعداد تعیین می شود ایکس 1 و ایکس 2. اگر در حین جمع تعدادی از ارقام یک حامل تشکیل شود، فرض می کنیم که جمع کننده به حالت منتقل شده است. q یکی . اگر حمل وجود نداشته باشد، فرض می کنیم که جمع کننده در حالت است q 0 .

جمع کننده توسط یک جدول داده می شود.

q

آ

q 0

q 1

q 0 , 0

q 0 , 1

q 0 , 1

q 1 , 0

q 0 , 1

q 1 , 0

q 1 , 0

q 1 , 1

نمودار مور یک جمع کننده ترتیبی در شکل نشان داده شده است. 5.

توجه داشته باشید که کاراکترهای ورودی و خروجی قبلاً کدگذاری شده اند. ما حالت ها را به صورت زیر رمزگذاری می کنیم: (q 0)=0, (q 1) = 1. بنابراین جمع کننده ترتیبی توسط دو تابع بولی به دست می آید که جدول صدق آن به صورت زیر است:

ایکس 1

ایکس 2

z

مثال 3. طرح مقایسه برابری.

مدار مقایسه برابری دستگاهی است که دو عدد را با هم مقایسه می کند. ایکس 1 و ایکس 2، در سیستم باینری داده شده است. این دستگاه به شرح زیر عمل می کند. در ورودی دستگاه به ترتیب، با شروع از بالاترین، ارقام اعداد تغذیه می شوند. ایکس 1 و ایکس 2. این رتبه ها با هم مقایسه می شوند. اگر بیت ها مطابقت داشته باشند، سیگنال خروجی 0 در خروجی مدار تشکیل می شود، در غیر این صورت سیگنال 1 در خروجی ظاهر می شود، واضح است که ظاهر شدن 1 در دنباله خروجی به این معنی است که اعداد مقایسه شده ایکس 1 و ایکس 2 ناهمسان. اگر دنباله خروجی صفر باشد و طول آن با تعداد ارقام اعداد مقایسه شده مطابقت داشته باشد، ایکس 1 و ایکس 2 .

برای این ماشین ایکس ={00, 01, 10, 11}; Y ={0,1}.

عملکرد مدار توسط دو حالت تعیین می شود. دولت q 0 مطابق با برابری بیت های مقایسه شده فعلی است. در این حالت دستگاه در همان حالت باقی می ماند. اگر در لحظه بعد ارقام مقایسه شده متفاوت باشند، خودکار به حالت جدیدی می رود q 1 و در آن باقی می ماند، زیرا به این معنی است که اعداد متفاوت هستند. بنابراین، طرح مقایسه را می توان با جدول مشخص کرد:

q

ایکس

q 0

q 1

q 0 , 0

q 1 , 1

q 1 , 1

q 1 , 1

q 1 , 1

q 1 , 1

q 0 , 0

q 1 , 1

نمودار مور طرح مقایسه برای برابری در شکل نشان داده شده است. 6.

ما حالت ها را به صورت زیر رمزگذاری می کنیم: (q 0)=0, (q 1) = 1. به خودکار دو عملکرد داده می شود.

ایکس 1

ایکس 2

z

مثال 4. طرح مقایسه نابرابری.

مدار مقایسه نابرابری دستگاهی است که به شما امکان می دهد بفهمید که آیا مقایسه شده است یا خیر ایکس 1 و ایکس 2، و اگر مساوی نیستند، بفهمید که کدام یک از دیگری بزرگتر است. این دستگاه دارای دو ورودی و دو خروجی می باشد. سیگنال های خروجی y 1 (تی ) و y 2 (تی ) طبق قوانین زیر تعیین می شوند:

y 1 (تی )=y 2 (تی )=0 اگر ایکس 1 (تی )=ایکس 2 (تی );

y 1 (تی )=1, y 2 (تی )=0 اگر ایکس 1 (تی )>ایکس 2 (تی )، به این معنا که ایکس 1 (تی )=1, ایکس 2 (تی )=0;

y 1 (تی )=0, y 2 (تی )=1 اگر ایکس 1 (تی )<ایکس 2 (تی )، به این معنا که ایکس 1 (تی )=0, ایکس 2 (تی )=1.

بنابراین، هنگام اعمال به ورودی مدار مقایسه برای نابرابری اعداد ایکس 1 =ایکس 1(l)… ایکس 1 (تی ) و ایکس 2 =ایکس 2(l)… ایکس 2 (تی ) ارقام این اعداد به ترتیب با هم مقایسه می شوند و از بالاترین ها شروع می شوند. سیگنال های خروجی طبق قوانین فوق فرموله می شوند. علاوه بر این، اگر دنباله خروجی از جفت صفر تشکیل شده باشد، پس ایکس 1 =ایکس 2. اگر اولی با صفر متفاوت باشد، جفت دارای فرم است , () سپس ایکس 1 >ایکس 2 (ایکس 1 <ایکس 2).

از توضیحات مدار چنین بر می آید که

ایکس ={00, 01, 10, 11}, Y ={00, 01, 10}.

حالت طرحواره به صورت زیر تعریف می شود. ما آن را در زمان اولیه فرض می کنیم تی =1 ماشین در حالت است q 1 . اگر ارقام اعداد با هم مقایسه شوند ایکس 1 و ایکس 2 مطابقت دهید، سپس دستگاه در این حالت باقی می ماند. توجه داشته باشید که سیگنال 00 در خروجی ظاهر می شود.اگر رقم عدد ایکس 1 کمتر (بزرگتر از) رقم مربوط به عدد خواهد بود ایکس 2، سپس دستگاه وارد حالت می شود q 2 (q 3). در این حالت سیگنال 01 (10) در خروجی ظاهر می شود. در آینده، هنگام ارسال ارقام باقی مانده از اعداد ایکس 1 و ایکس 2 به ورودی های خودکار، خودکار در حالت باقی می ماند q 2 (q 3) و نماد خروجی 10 (01) را تولید کنید. از موارد فوق نتیجه می گیرد که طرح مقایسه برای نابرابری را می توان با جدول مشخص کرد:

q

ایکس

q 1

q 2

q 3

q 1 , 00

q 2 , 01

q 3 , 10

q 2 , 01

q 2 , 01

q 3 , 10

q 3 , 10

q 2 , 01

q 3 , 10

q 1 , 00

q 2 , 01

q 3 , 10

نمودار مور مربوطه در شکل نشان داده شده است. 7.

الفبای ورودی و خروجی قبلاً در اینجا کدگذاری شده اند. ایالت ها q 1 , q 2 و q 3 رمزگذاری: 1 (q 1)=00, (q 2)=01, (q 3)=10.

بنابراین، این مدار را می توان با سیستمی متشکل از چهار تابع بولی که به چهار متغیر بستگی دارد، تعریف کرد. این توابع تا حدی توسط جدول حقیقت تعریف و ارائه شده اند

ایکس 1

ایکس 2

z 1

z 2

در جدول، نمادها * مجموعه ای از متغیرها را علامت گذاری می کنند ایکس 1 , ایکس 2 , z 1 , z 2، که بر روی آن توابع 1 , 2 , 1 , 2 تعریف نشده است. بگذاریم مقادیر تابع 1 , 2 , 1 , 2 در این مجموعه ها برابر با 1 است.

این مقاله ماشین‌های حالت ساده و پیاده‌سازی آن‌ها در C++ را با استفاده از ساختارهای سوئیچ، جداول زمان اجرا و کتابخانه Boost Statechart مورد بحث قرار می‌دهد.

مقدمه

به طور کلی، ماشین حالت محدود (ماشین وضعیت محدود)، از نظر کاربر، یک جعبه سیاه است که می توانید چیزی را به آن منتقل کنید و چیزی را از آنجا دریافت کنید. این یک انتزاع بسیار راحت است که به شما امکان می دهد یک الگوریتم پیچیده را پنهان کنید، علاوه بر این، ماشین های حالت بسیار کارآمد هستند.

اتوماتای ​​محدود به صورت نمودارهایی متشکل از حالت ها و انتقال ها نشان داده می شوند. بگذارید با یک مثال ساده توضیح دهم:

همانطور که احتمالا حدس زدید، این یک نمودار وضعیت لامپ است. حالت اولیه با یک دایره سیاه نشان داده می شود، انتقال با فلش ها، برخی از فلش ها امضا می شوند - اینها رویدادهایی هستند که پس از آن دستگاه به حالت دیگری تغییر می کند. بنابراین، بلافاصله از حالت اولیه، وارد حالت می شویم چراغ خاموش- لامپ روشن نیست. اگر دکمه را فشار دهید، دستگاه حالت خود را تغییر می دهد و پیکان مشخص شده را دنبال می کند دکمه فشار، به ایالت نور روشن- لامپ روشن است می توانید دوباره از این حالت با فلش، پس از فشار دادن دکمه، به حالت بروید چراغ خاموش.

میزهای پرش نیز به طور گسترده مورد استفاده قرار می گیرند:

کاربرد عملی ماشین آلات

ماشین های حالت به طور گسترده ای در برنامه نویسی استفاده می شوند. به عنوان مثال، تصور عملکرد دستگاه در قالب یک خودکار بسیار راحت است. این کار کد را ساده‌تر می‌کند و آزمایش و نگهداری آن را آسان‌تر می‌کند.

همچنین، خودکارهای محدود برای نوشتن انواع تجزیه کننده ها و تحلیلگرهای متن استفاده می شود، با کمک آنها می توانید به طور موثر زیر رشته ها را جستجو کنید، عبارات منظم نیز به یک خودکار متناهی ترجمه می شوند.

به عنوان مثال، ما یک خودکار برای شمارش اعداد و کلمات در یک متن پیاده سازی خواهیم کرد. برای شروع، اجازه دهید توافق کنیم که یک عدد دنباله ای از ارقام از 0 تا 9 با طول دلخواه باشد که با کاراکترهای فضای خالی (فضا، برگه، خوراک خط) احاطه شده است. یک کلمه دنباله ای با طول دلخواه متشکل از حروف و همچنین احاطه شده توسط کاراکترهای فضای خالی در نظر گرفته می شود.

یک نمودار را در نظر بگیرید:

از حالت اولیه به حالت می رسیم شروع کنید. کاراکتر فعلی را بررسی می کنیم و اگر یک حرف است، به حالت بروید کلمهدر امتداد فلش مشخص شده به عنوان حرف. این حالت فرض می کند که در لحظه ای که ما یک کلمه را در نظر می گیریم، تجزیه و تحلیل نمادهای بعدی یا این فرض را تأیید می کند یا آن را رد می کند. بنابراین، کاراکتر بعدی را در نظر بگیرید، اگر یک حرف است، وضعیت تغییر نمی کند (به فلش دایره ای که به عنوان علامت گذاری شده توجه کنید حرف). اگر کاراکتر یک حرف نیست، اما با یک کاراکتر فاصله مطابقت دارد، به این معنی است که فرض درست بود و کلمه را پیدا کردیم (ما در امتداد فلش حرکت می کنیم فضابه یک حالت شروع کنید). اگر نماد نه حرف باشد و نه فاصله، در این فرض اشتباه کرده ایم و دنباله ای که در نظر می گیریم یک کلمه نیست (ما از فلش پیروی می کنیم. ناشناسبه یک حالت پرش کنید).

قادر پرش کنیدما می مانیم تا زمانی که با یک کاراکتر فضایی مواجه شویم. بعد از اینکه شکاف پیدا شد، پیکان را دنبال می کنیم فضابه یک حالت شروع کنید. این برای رد شدن کامل از خطی که با الگوی جستجوی ما مطابقت ندارد ضروری است.

پس از انتقال به ایالت شروع کنید، چرخه جستجو از ابتدا تکرار می شود. شاخه تشخیص اعداد مشابه شاخه تشخیص کلمه است.

خودکار با استفاده از دستورالعمل های سوئیچ

اولین حالت ممکن است:

پس از آن، روی رشته تکرار می‌کنیم و کاراکتر فعلی را به خودکار وارد می‌کنیم. خود خودکار یک دستور سوئیچ است که ابتدا یک انتقال به بخش وضعیت فعلی را انجام می دهد. در داخل بخش یک ساختار if-else وجود دارد که بسته به رویداد (نویسه ورودی)، وضعیت فعلی را تغییر می دهد:

const size_t طول = text.length () ; برای (size_t i = 0 ; i != طول؛ ++ i) ( const char current = text[i] ; switch (وضعیت) ( case State_Start: if (std:: isdigit (current) ) ( state = State_Number; ) else if (std:: isalpha (جاری)) (state = State_Word; ) break ; case State_Number: if (std:: isspace (current)) (

در اینجا ما به نمودار نگاه می کنیم - وضعیت فعلی عدد، رویداد فضا(یک کاراکتر فاصله مواجه شد)، به این معنی که یک عدد پیدا شد:

FoundNumber() ; state = State_Start; ) else if (! std:: isdigit (current) ) ( state = State_Skip; ) break ; case State_Word: if (std:: isspace (current) ) ( FoundWord() ; state = State_Start; ) other if (! std:: isalpha (current) ) ( state = State_Skip; ) break ; case State_Skip: if (std::isspace (current) ) ( state = State_Start; ) break ; ))

نتیجه:

راندمان بالا

سهولت پیاده سازی برای الگوریتم های ساده

- نگهداری سخت

تفسیر زمان اجرا

ایده این رویکرد ساده است - شما باید یک جدول انتقال ایجاد کنید، آن را پر کنید، و سپس، هنگامی که یک رویداد رخ می دهد، وضعیت زیر را در جدول پیدا کنید و انتقال را انجام دهید:

enum رویدادها( Event_Space، Event_Digit، Event_Letter، Event_Unknown) ; void ProcessEvent (رویداد رویدادها) ; ... struct Transition ( State BaseState_; Events Event_; State TargetState_; ) ; void AddTransition (ایالات از ایالت، رویداد رویدادها، ایالات به ایالت) ; ... typedef std::vector< transition>TransitionTable; TransitionTable Transitions_; ایالات CurrentState_;

پر کردن جدول:

AddTransition(State_Start، Event_Digit، State_Number) ; AddTransition(State_Start، Event_Letter، State_Word) ; AddTransition(State_Number، Event_Space، State_Start) ; AddTransition(State_Number، Event_Letter، State_Skip); AddTransition(State_Number, Event_Unknown, State_Skip) ; AddTransition(State_Word، Event_Space، State_Start) ; AddTransition(State_Word، Event_Digit، State_Skip) ; AddTransition(State_Word، Event_Unknown، State_Skip) ; AddTransition(State_Skip، Event_Space، State_Start) ;

خیلی واضح معلوم شد پرداخت برای وضوح عملکرد کندتر دستگاه خواهد بود که اتفاقاً اغلب مهم نیست.

به طوری که خودکار، در صورت وقوع رویدادهای خاص، می تواند کدی را مطلع کند، می توانید اطلاعات مربوط به انتقال را به ساختار اضافه کنید ( انتقالنشانگر تابع ( عمل) که نامیده می شود:

typedef void (DynamicMachine:: * Action) () ; struct Transition ( State BaseState_; Events Event_; State TargetState_; Action Action_; ) ; ... void AddTransition(States fromState, Events event, State toState, Action action) ; ... AddTransition (State_Number, Event_Space, State_Start, & DynamicMachine:: FoundNumber ) ;

نتیجه:

انعطاف پذیری و دید

نگهداری راحت تر

- عملکرد کمتر در مقایسه با بلوک های سوئیچ

تفسیر زمان اجرا بهینه سازی سرعت

آیا می توان دید را با سرعت ترکیب کرد؟ بعید است که یک خودکار به اندازه یک خودکار مبتنی بر بلوک های سوئیچ کارآمد باشد، اما می توان شکاف را از بین برد. برای انجام این کار، لازم است یک ماتریس از جدول بسازید، به طوری که برای به دست آوردن اطلاعات در مورد انتقال، جستجو را انجام ندهید، بلکه با تعداد حالت و رویداد انتخاب کنید:

نتیجه با هزینه مصرف حافظه به دست می آید.

نتیجه:

انعطاف پذیری، دید

تاثير گذار

- مصرف حافظه (احتمالاً ناچیز)

استیتچارت را تقویت کنید

ما چندین روش برای پیاده سازی یک ماشین حالت را خودتان مورد بحث قرار دادیم. برای تغییر، پیشنهاد می‌کنم یک کتابخانه برای ساخت خودکار از Boost در نظر بگیرید. این کتابخانه بسیار قدرتمند است، ویژگی های پیشنهادی به شما امکان می دهد ماشین های حالت محدود بسیار پیچیده بسازید. ما به طور مختصر به آن نگاه خواهیم کرد.

پس بیایید ابتدا رویدادها را تعریف کنیم:

رویدادهای فضای نام ( ساختار رقم : boost::statechart::event< Digit>( ) ؛ struct Letter : boost::statechart::event< Letter>( ) ؛ struct Space: boost::statechart::event< Space>( ) ؛ structUnknown : boost::statechart::event< Unknown> { } ; }

خود ماشین (توجه داشته باشید که پارامتر دوم الگو حالت اولیه است):

struct Machine : boost::statechart::state_machine< Machine, States:: Start > { } ;

و خود ایالت ها. در داخل حالت ها، لازم است نوعی تعریف شود که انتقال ها را توصیف کند ( واکنش ها، و اگر چندین انتقال وجود دارد، آنها را در لیست نوع boost::mpl::list لیست کنید. پارامتر قالب دوم ساده_حالت- نوع توصیف ماشین انتقال ها با پارامترهای قالب انتقال، جفت توصیف می شوند رویداد - ایالت بعدی:

وضعیت های فضای نام ( ساختار Start : boost::statechart::simple_state< Start, Machine> < boost:: statechart :: transition < Events:: Digit , States:: Number >< Events:: Letter , States:: Word >> واکنش؛ ) ؛ struct Number : boost::statechart::simple_state< Number, Machine>( تقویت typedef::mpl::list< boost:: statechart :: transition < Events:: Space , States:: Start >, boost::statetechart::transition< Events:: Letter , States:: Skip >, boost::statetechart::transition< Events:: Unknown , States:: Skip >> واکنش؛ ) ؛ struct Word : boost::statechart::simple_state< Word, Machine>( تقویت typedef::mpl::list< boost:: statechart :: transition < Events:: Space , States:: Start >, boost::statetechart::transition< Events:: Digit , States:: Skip >, boost::statetechart::transition< Events:: Unknown , States:: Skip >> واکنش؛ ) ؛ struct Skip: boost::statechart::simple_state< Skip, Machine>( typedef boost::statechart::transition< Events:: Space , States:: Start >واکنش ها؛ ) ؛ )

دستگاه ساخته شده است، فقط مقداردهی اولیه آن باقی مانده است و می توانید از موارد زیر استفاده کنید:

ماشین آلات؛ machine.initiate(); ...machine .process_event(Events::Space() );

نتیجه:

انعطاف پذیری، قابلیت گسترش

- بهره وری

کارایی

من یک برنامه آزمایشی نوشتم تا سرعت اتوماتای ​​ساخته شده را بررسی کنم. از طریق دستگاه‌ها، متن را به اندازه 17 مگابایت رانندگی کردم. در اینجا نتایج این اجرا آمده است:

بارگیری متن متن: 17605548 بایت 0.19 S در حال اجرا کلمات Boostmachine: 998002 ، اعداد: 6816 0.73 S در حال اجرا DynamicMachine کلمات: 998002 ، اعداد: 6816 0.56 S در حال اجرا سریع کلمات: 998002 ، اعداد: 6816 0.29 S: Numbers SimplemachineChineChineChine02 س

آنچه که بررسی نشده باقی مانده است

چندین پیاده سازی دیگر از اتوماتای ​​محدود کشف نشده باقی ماندند (من http://www.rsdn.ru/article/alg/Static_Finite_State_Machine.xml و http://www.rsdn.ru/article/alg/FiniteStateMachine.xml را توصیه می کنم)، ژنراتورهایی که ساخت خودکار از توضیحات، کتابخانه Meta State Machine از نسخه Boost 1.44.0، و همچنین توضیحات رسمی ماشین‌های حالت. خواننده کنجکاو می تواند با تمام موارد فوق آشنا شود.

نظریه اتوماتا شاخه ای از ریاضیات گسسته است که مدل های مبدل اطلاعات گسسته را مطالعه می کند. چنین مبدل‌هایی هم دستگاه‌های واقعی (کامپیوتر، موجودات زنده) و هم دستگاه‌های خیالی (نظریه‌های بدیهی، ماشین‌های ریاضی) هستند. در اصل، یک ماشین حالت محدود را می توان به عنوان یک دستگاه توصیف کرد م که دارای کانال های ورودی و خروجی می باشد در حالی که در هر یک از ممان های زمانی گسسته به نام لحظه های ساعت در یکی از حالت های نهایی قرار دارد.

در کانال ورودی در هر زمان تی =1، 2، ... به دستگاه م سیگنال های ورودی می رسند (از مجموعه محدودی از سیگنال ها). قانون تغییر حالت به لحظه بعدی زمان بسته به سیگنال ورودی و وضعیت دستگاه در لحظه فعلی تنظیم می شود. سیگنال خروجی به وضعیت و سیگنال ورودی در زمان فعلی بستگی دارد (شکل 1).

ماشین حالت یک مدل ریاضی از دستگاه های پردازش اطلاعات گسسته واقعی است.

ماشین حالت سیستم نامیده می شود A= (ایکس , س , Y , , )، جایی که ایکس , س , Y مجموعه های محدود غیر خالی دلخواه هستند و و - توابع، که از جمله:

    بسیاری از ایکس ={آ 1 , ..., آ متر ) نامیده میشود الفبای ورودی ، و عناصر آن هستند سیگنال های ورودی ، دنباله آنها در است عبارات جذاب ;

    بسیاری از س ={q 1 , ..., q n ) نامیده میشود بسیاری از ایالت ها خودکار و عناصر آن - ایالت ها ;

    بسیاری از Y ={ب 1 , ..., ب پ ) نامیده میشود الفبای خروجی ، عناصر آن هستند سیگنال های خروجی ، دنباله آنها هستند کلمات خروجی ;

    عملکرد : ایکس س س تماس گرفت تابع انتقال ;

    عملکرد :ایکس س Y تماس گرفت تابع خروجی .

به این ترتیب، (ایکس , q )س , (ایکس , q )Y برای  ایکس ایکس , q س .

یک دستگاه خیالی با ماشین حالت مرتبط است که به صورت زیر عمل می کند. می تواند در یک حالت از مجموعه باشد س ، سیگنال ها را از مجموعه دریافت کنید ایکس و سیگنال های یک مجموعه را صادر می کند Y .

2. روش های تعریف خودکار محدود

چندین روش معادل برای تعریف خودکار انتزاعی وجود دارد که سه مورد از آنها عبارتند از: جدولی , هندسی و کاربردی .

2.1. تعیین جدول ماشین

از تعریف خودکار برمی آید که همیشه می توان آن را با یک جدول با دو ورودی که شامل تی خطوط و پ ستون ها، جایی که در تقاطع ستون q و خطوط آ مقادیر تابع ارزش دارند (آ من , q j ), (آ من , q j ).

q

آ

q 1

q j

q n

آ 1

(آ 1 , q 1), (آ 1 , q 1)

(آ 1 , q j ), (آ 1 , q j )

(آ 1 , q n ), (آ 1 , q n )

آ من

(آ من , q 1), (آ من , q 1)

(آ من , q j ), (آ من , q j )

(آ من , q n ), (آ من , q n )

آ متر

(آ متر , q 1), (آ متر , q 1)

(آ متر , q j ), (آ متر , q j )

(آ متر , q n ), (آ متر , q n )

2.2. تعریف خودکار با نمودار مور

روش دیگر برای تعیین یک ماشین حالت محدود، گرافیکی است، یعنی استفاده از نمودار. خودکار به عنوان یک گراف جهت دار نشاندار شده نشان داده می شود جی(س , D ) با رئوس زیاد س و قوس های زیادی D ={(q j , (آ من , q j ))| q j س , آ من ایکس ، در حالی که قوس ( q j , (آ من , q j )) با یک جفت ( آ من , (آ من , q j )). بدین ترتیب، با این روش، حالت‌های خودکار به‌وسیله دایره‌هایی به تصویر کشیده می‌شوند که نمادهای حالت‌ها در آن وارد می‌شوند. q j (j = 1, …, n ). از هر دایره انجام می شود تی فلش ها (لبه های جهت دار) یک به یک مربوط به کاراکترهای الفبای ورودی ایکس ={آ 1 , ..., آ متر ). فلش مربوط به حرف آ من ایکس و از دایره خارج شد q j س ، جفت ( آ من , (آ من , q j ))، و این فلش به یک دایره مربوط به (آ من , q j ).

رسم حاصل نامیده می شود نمودار خودکار یا، نمودار مور . برای اتومات های نه چندان پیچیده، این روش گویاتر از جدولی

امروز در مورد مسلسل ها صحبت خواهیم کرد، اما به هیچ وجه آنهایی که سربازان در دست دارند ارتش روسیه. ما در مورد سبک جالب برنامه نویسی میکروکنترلرها مانند برنامه نویسی خودکار صحبت خواهیم کرد. به عبارت دقیق تر، این حتی یک سبک برنامه نویسی نیست، بلکه یک مفهوم کامل است که به لطف آن یک برنامه نویس میکروکنترلر می تواند زندگی خود را بسیار آسان تر کند. به همین دلیل بسیاری از وظایف ارائه شده به برنامه نویس بسیار آسان تر و ساده تر حل می شود و برنامه نویس را از سردرد خلاص می کند. به هر حال، برنامه نویسی خودکار اغلب نامیده می شود تکنولوژی SWITCH.

می خواهم توجه داشته باشم که محرک نوشتن این پست بود سری مقالات در مورد تکنولوژی SWITCH ولادیمیر تاتارچفسکی. مجموعه مقالات «کاربرد فناوری سوئیچ در توسعه کاربردی» نام دارد نرم افزاربرای میکروکنترلرها "بنابراین در این مقاله سعی خواهم کرد در بیشتر موارد مثالی از یک کد کار و توضیحات آن را ارائه دهم.

به هر حال، من یک سری مقاله در مورد برنامه نویسی برنامه ریزی کرده ام که در آن تکنیک های برنامه نویسی میکروکنترلرهای ABP را با جزئیات در نظر خواهم گرفت. از دست نده…. خب بریم!

برنامه به صورت متوالی دستورات تعیین شده توسط برنامه نویس را اجرا می کند. برای معمولی برنامه کامپیوتریزمانی که برنامه کامل شده و اجرای خود را متوقف کرده، در حالی که نتایج کار خود را بر روی مانیتور نمایش می دهد، کاملا طبیعی است.

یک برنامه میکروکنترلر نمی تواند به سادگی به اجرای خود پایان دهد. تصور کنید پخش کننده یا ضبط صوت را روشن کرده اید. شما دکمه پاور را فشار داده اید، آهنگ مورد نظر را انتخاب کرده اید و از موسیقی لذت می برید. با این حال، زمانی که موسیقی دیگر پرده گوش شما را به هم نمی زند، پخش کننده یخ می زند و به هیچ وجه به فشار دادن دکمه ها و حتی بیشتر از آن به رقص شما با تنبور واکنشی نشان نمی دهد.

و این چیه؟ همه چیز خوب است - کنترلر، همانی که در اعماق پخش کننده شما قرار دارد، به تازگی اجرای برنامه خود را به پایان رسانده است. در اینجا می توانید ببینید که چقدر ناراحت کننده است.

بنابراین از اینجا نتیجه می گیریم که برنامه برای میکروکنترلر به سادگی نباید متوقف شود. در اصل، باید یک حلقه بی نهایت باشد - فقط در این صورت پخش کننده ما به درستی کار می کند. بعد، من به شما نشان خواهم داد که چه طرح هایی هستند کد برنامهبرای میکروکنترلرها، اینها حتی طرح نیستند، بلکه برخی از سبک های برنامه نویسی هستند.

سبک های برنامه نویسی

"سبک های برنامه نویسی" نامفهوم به نظر می رسد، اما اوه خوب. با این چی می خوام بگم بیایید تصور کنیم که یک نفر تا به حال برنامه نویسی نکرده است، یعنی به طور کلی یک ساختگی کامل.

این شخص کتاب های زیادی در مورد برنامه نویسی خوانده است، تمام ساختارهای اساسی زبان را یاد گرفته است.او اطلاعات را ذره ذره جمع آوری کرد، زیرا اکنون دسترسی به اطلاعات نامحدود است. همه اینها خوب است، اما اولین برنامه های او چگونه خواهد بود؟ به نظر من او فلسفه نخواهد کرد، بلکه مسیر ساده به پیچیده را دنبال خواهد کرد.

بنابراین این سبک‌ها مراحلی هستند که از یک سطح ساده به یک سطح پیچیده‌تر، اما در عین حال مؤثرتر، منتهی می‌شوند.

اولش به هیچ کدوم فکر نکردم ویژگی های طراحیبرنامه ها. من فقط منطق برنامه را شکل دادم - یک فلوچارت کشیدم و کد را نوشتم. از آنچه به طور مداوم در سراسر یک چنگک جمع کردن. اما این اولین باری بود که من حمام بخار نگرفتم و از سبک "حلقه ساده" استفاده کردم، سپس شروع به استفاده از وقفه کردم، سپس اتومات ها وجود داشت و ما رفتیم ...

1. حلقه زدن ساده. برنامه در این مورد بدون هیچ گونه پیچیدگی حلقه می شود و این مزایا و معایب خود را دارد. به علاوه فقط در سادگی رویکرد، شما نیازی به اختراع طرح های حیله گر ندارید، همانطور که فکر می کنید می نویسید (به تدریج قبر خود را حفر کنید).

Void main(void) ( initial_AL(); //initialization of پیرامون while(1) (Leds_BLINK(); //function of flasher LED signal_on(); //function of روشن کردن سیگنال signal_off(); // عملکرد خاموش کردن سیگنال l=button()؛ //متغیر مسئول فشار دادن دکمه‌ها سوئیچ(l) //بسته به مقدار متغیر، یک عمل انجام می‌شود (مورد 1: ( Deistvie1();/ /به جای یک تابع، ممکن است وجود داشته باشد عملگر شرطی deistvie2(); //یا چند شاخه دیگر سوئیچ case Deistvie3(); deistvie4(); deistvie5(); ) مورد 2: ( Deistvie6(); Deistvie7(); Deistvie8(); Deistvie9(); Deistvie10(); ); . . . . . . . . ))))

نقطه کار برنامه به ترتیب حرکت می کند. در این حالت، تمام اقدامات، شرایط و چرخه ها به صورت متوالی اجرا می شوند. کد شروع به کند شدن می کند، شما باید شرایط اضافی زیادی را وارد کنید، در نتیجه درک را پیچیده می کنید.

همه اینها به شدت برنامه را گیج می کند و شرایط را از کد درهم می کشد. در نتیجه، این کد نمی تواند اضافه یا حذف شود، مانند یک قطعه یکپارچه می شود. البته، زمانی که حجم زیاد نباشد، کد را می توان تغییر داد، اما هر چه بیشتر سخت تر باشد.

با این رویکرد، من چندین برنامه نوشتم، آنها بزرگ نبودند و کاملاً کار می کردند، اما دیده شدن چیزهای زیادی باقی می ماند. برای اضافه کردن شرایط جدید، مجبور شدم کل کد را بیل کنم، زیرا همه چیز گره خورده بود. این باعث اشتباهات و سردردهای زیادی شد. کامپایلر در اسرع وقت قسم خورد، اشکال زدایی چنین برنامه ای به جهنم تبدیل شد.

2. چرخه + وقفه.

شما می توانید تا حدی چرخه ترمز بی نهایت را با استفاده از وقفه حل کنید. وقفه ها به شکستن دایره باطل کمک می کند، به شما کمک می کند یک رویداد مهم را از دست ندهید، قابلیت های اضافی را اضافه کنید (وقفه های تایمر، وقفه های خارجی).

فرض کنید می‌توانید پردازش دکمه‌ها را متوقف کنید یا یک رویداد مهم را در یک وقفه ردیابی کنید. در نتیجه، برنامه بصری تر می شود اما کمتر گیج کننده نیست.

متأسفانه، وقفه شما را از آشفتگی که برنامه به آن تبدیل می کند، نجات نمی دهد. نمی توان آن چیزی را که یک کل واحد است به اجزاء تقسیم کرد.

3. برنامه نویسی خودکار.

بنابراین به موضوع اصلی این مقاله می رسیم. برنامه نویسی در ماشین های حالت محدود، برنامه را از کاستی های ذاتی در دو مثال اول نجات می دهد. برنامه ساده تر می شود، تغییر آن آسان است.

برنامه ای که به سبک خودکار نوشته می شود مانند یک سوئیچ است که بسته به شرایط به یک حالت یا حالت دیگر تغییر می کند. تعداد حالت ها در ابتدا برای برنامه نویس مشخص است.

تقریباً مانند یک کلید چراغ است. دو حالت روشن و خاموش و دو حالت روشن و خاموش وجود دارد. خوب، اول چیزها.

پیاده سازی چند وظیفه ای در فناوری سوئیچ.

میکروکنترلر قادر به کنترل بار، چشمک زدن LED ها، ردیابی ضربه های کلید و موارد دیگر است. اما چگونه می توان همه اینها را همزمان انجام داد؟ راه حل های زیادی برای این موضوع وجود دارد. ساده ترین این موارد که قبلاً ذکر کردم استفاده از وقفه است.

در طول برنامه، زمانی که وقفه رخ می دهد، کنترل کننده از اجرای کد برنامه منحرف می شود و به طور خلاصه قطعه دیگری از برنامه را که وقفه مسئول آن است، اجرا می کند. وقفه کار می کند، سپس نقطه کار برنامه از جایی که کنترل کننده با وقفه از آنجا قطع شده است ادامه می یابد (خود کلمه نشان می دهد که کنترل کننده قطع شده است).

یکی دیگر از راه های اجرای چند وظیفه ای استفاده از آن است سیستم های عامل. بله، سیستم‌عامل‌های کوچک از قبل ظاهر شده‌اند که می‌توان از آن‌ها در کنترل‌کننده‌های کم مصرف استفاده کرد. اما اغلب این روش تا حدودی زائد به نظر می رسد. به هر حال، چرا منابع کنترل کننده را با کارهای غیرضروری هدر می دهیم، در حالی که امکان گذراندن آن با خونریزی کم وجود دارد.

در برنامه هایی که با استفاده از فناوری سوئیچ نوشته شده اند، چنین "توهم" چندوظیفه ای به لطف سیستم پیام رسانی به دست می آید. من "توهم" را نوشتم زیرا واقعاً چنین است، زیرا برنامه از نظر فیزیکی نمی تواند بخش های مختلف کد را همزمان اجرا کند. در مورد سیستم پیام رسان کمی بیشتر صحبت خواهم کرد.

سیستم پیام رسانی

شما می توانید چندین فرآیند را از بین ببرید و با استفاده از سیستم پیام رسانی، توهم چندوظیفگی را ایجاد کنید.

فرض کنید به برنامه ای نیاز داریم که در آن LED روشن شود. در اینجا ما دو ماشین داریم، بیایید آنها را LEDON نامگذاری کنیم - دستگاه مسئول روشن کردن LED و دستگاه LEDOFF - دستگاهی که مسئول خاموش کردن LED است.

هر یک از خودکارها دارای دو حالت هستند، یعنی خودکار می تواند در حالت فعال یا غیرفعال باشد، مانند سوئیچ چاقو یا روشن یا خاموش.

هنگامی که یک دستگاه فعال می شود، LED روشن می شود، زمانی که دستگاه دیگر فعال می شود، LED خاموش می شود. یک مثال کوچک را در نظر بگیرید:

Int main(void) ( INIT_PEREF(); //راه‌اندازی تجهیزات جانبی (LED) InitGTimers(); //راه‌اندازی تایمرها InitMessages(); //راه‌اندازی مکانیسم پردازش پیام InitLEDON(); //راه‌اندازی اولیه خودکار LEDON InitLEDOFF(); // مقداردهی اولیه خودکار LEDOFF SendMessage(MSG_LEDON_ACTIVATE)؛ //فعال کردن خودکار LEDON sei(); //فعال کردن وقفه ها //حلقه اصلی برنامه while(1) (ProcessLEDON(); //تکرار LEDON automaton ProcessLEDOFF(); //تکرار خودکار LEDOFF ProcessMessages (); //پردازش پیام؛ )

در خطوط 3-7، مقداردهی اولیه های مختلفی رخ می دهد، بنابراین ما در حال حاضر علاقه خاصی به این موضوع نداریم. اما پس از آن موارد زیر اتفاق می افتد: قبل از شروع حلقه اصلی (در حالی که (1))، ما یک پیام به خودکار ارسال می کنیم.

ارسال پیام (MSG_LEDON_ACTIVATE)

مسئول روشنایی LED بدون این قدم کوچک، گردی ما کار نخواهد کرد. در مرحله بعد، حلقه while بی نهایت اصلی بخش اعظم کار را انجام می دهد.

انحراف کوچک:

پیام سه حالت دارد. یعنی وضعیت پیام می تواند غیرفعال، تنظیم شده اما غیرفعال و فعال باشد.

معلوم شد که پیام در ابتدا غیر فعال بوده است، زمانی که پیام را ارسال کردیم، وضعیت "نصب شده اما غیر فعال" را دریافت کرده است. و این موارد زیر را به ما می دهد. هنگامی که برنامه به صورت متوالی اجرا می شود، خودکار LEDON پیامی را دریافت نمی کند. یک تکرار غیرفعال خودکار LEDON رخ می دهد که در آن پیام به سادگی قابل دریافت نیست. از آنجایی که پیام دارای وضعیت "نصب شده اما غیر فعال" است، برنامه به اجرای خود ادامه می دهد.

پس از بیکار بودن همه خودکارها، صف به تابع ()ProcessMessages می رسد. این تابع همیشه پس از تکمیل تمام تکرارهای خودکار در انتهای حلقه قرار می گیرد. تابع ProcessMessages () به سادگی پیام را از حالت "تنظیم اما غیر فعال" به حالت "فعال" تغییر می دهد.

هنگامی که حلقه بی نهایت دور دوم را کامل می کند، تصویر در حال حاضر کاملاً متفاوت است. تکرار خودکار ProcessLEDON دیگر بیکار نخواهد بود. دستگاه قادر خواهد بود پیام را دریافت کند، به حالت روشن تبدیل شود و همچنین پیام را به نوبه خود ارسال کند. خطاب به دستگاه LEDOFF و چرخه زندگیپیام ها تکرار خواهد شد

می‌خواهم توجه داشته باشم که پیام‌هایی که حالت «فعال» دارند، هنگامی که با عملکرد ProcessMessages ملاقات می‌کنند، از بین می‌روند. بنابراین، پیام فقط توسط یک خودکار قابل دریافت است. نوع دیگری از پیام ها وجود دارد - اینها پیام های پخش هستند، اما من آنها را در نظر نمی گیرم، آنها همچنین در مقالات تاتارچفسکی به خوبی پوشش داده شده اند.

تایمرها

با پیام‌رسانی مناسب، می‌توانیم ترتیب کار ماشین‌های حالت را کنترل کنیم، اما بدون پیام به تنهایی نمی‌توانیم کار کنیم.

ممکن است متوجه شده باشید که قطعه کد مثال قبلی آنطور که در نظر گرفته شده کار نخواهد کرد. ماشین ها پیام ها را رد و بدل می کنند، LED ها تغییر می کنند، اما ما این را نخواهیم دید. ما فقط یک LED کم نور خواهیم دید.

این به این دلیل است که ما به پردازش صالح تاخیرها فکر نکردیم. از این گذشته ، روشن و خاموش کردن متناوب LED ها برای ما کافی نیست ، LED باید در هر حالت مثلاً برای یک ثانیه بماند.

الگوریتم به صورت زیر خواهد بود:

برای بزرگنمایی می توانید کلیک کنید

فراموش کردم در این بلوک دیاگرام اضافه کنم که وقتی تایمر تیک می زند، البته، یک عمل انجام می شود - روشن کردن LED یا خاموش کردن آن.

1. با دریافت پیام وارد حالت می شویم.

2. ما قرائت های تایمر/ شمارنده را بررسی می کنیم، اگر تیک داشته باشد، سپس عمل را انجام می دهیم، در غیر این صورت فقط به خودمان پیام می دهیم.

3. ما به خودکار بعدی پیام می دهیم.

4. خارج شوید

در ورودی بعدی همه چیز تکرار می شود.

برنامه تکنولوژی SWITCH. سه مرحله

و بیایید یک برنامه در اتوماتای ​​محدود بنویسیم و برای این کار فقط سه مورد نیاز داریم مراحل ساده. این برنامه ساده خواهد بود، اما ارزش شروع با چیزهای ساده را دارد. ما برنامه مناسبدارای LED سوئیچینگ این خیلی مثال خوبپس بیایید چیز جدیدی اختراع نکنیم.

من برنامه را به زبان C می نویسم، اما این به هیچ وجه به این معنی نیست که در اتوماتای ​​محدود شما باید فقط به زبان C بنویسید، استفاده از هر زبان برنامه نویسی دیگری کاملاً امکان پذیر است.

این برنامه ماژولار خواهد بود و بنابراین به چندین فایل تقسیم می شود. ماژول های ما خواهند بود:

  • ماژول حلقه اصلی برنامه حاوی فایل های leds_blink.c، HAL.c، HAL.h است.
  • ماژول تایمر حاوی فایل های timers.c، timers.h است
  • ماژول پردازش پیام حاوی فایل‌های messages.c، messages.h
  • ماژول ماشین 1 حاوی فایل‌های ledon.c، ledon.h است
  • ماژول ماشین 2 حاوی فایل های ledoff.c است، ledoff .h

مرحله 1.

ما یک پروژه ایجاد می کنیم و بلافاصله فایل های ماژول های استاتیک خود را به آن متصل می کنیم: timers.c، timers.h، messages.c، messages.h.

فایل leds_blink.c ماژول چرخه اصلی برنامه.

#include "hal.h" #include "messages.h" //message processing module #include "timers.h" //timers module //Timer interrupts //############# ############################################### ############################ ISR(TIMER0_OVF_vect) // وقفه انتقال بردار (سرریز تایمر شمارنده T0) ( ProcessTimers(); / / کنترل کننده وقفه تایمر) //#################################################################### ############################################### (Vid) ( INIT_PEREF(); //آغاز کردن حاشیه (LED) InitGTimers(); //آغازسازی تایمرها InitMessages(); //آغازسازی مکانیسم پردازش پیام InitLEDON(); //راه‌اندازی اولیه خودکار LEDON InitLEDOFF(); StartGTimer( TIMER_SEK)؛ //شروع تایمر SendMessage(MSG_LEDON_ACTIVATE)؛ //فعال کردن خودکار FSM1 sei(); //فعال کردن وقفه ها //حلقه اصلی برنامه while(1) (ProcessLEDON(); //تکرار خودکار LEDON ProcessLEDOFF(); ProcessMessages(); //پردازش پیام؛ )

در خطوط اول، ماژول های باقی مانده به برنامه اصلی متصل می شوند. در اینجا می بینیم که ماژول تایمر و ماژول پردازش پیام به هم متصل شده اند. بعدی در برنامه بردار وقفه سرریز است.

از خط int main (void) می توان گفت که برنامه اصلی شروع می شود. و با مقداردهی اولیه همه چیز و هر کس شروع می شود. در اینجا ما لوازم جانبی را مقداردهی اولیه می کنیم، یعنی مقادیر اولیه را روی پورت های ورودی/خروجی مقایسه کننده و سایر محتویات کنترلر تنظیم می کنیم. همه اینها توسط تابع INIT_PEREF انجام می شود، ما آن را در اینجا اجرا می کنیم، اگرچه بدنه اصلی آن در فایل hal.c است.

در مرحله بعد، مقداردهی اولیه تایمرها، ماژول پردازش پیام و مقداردهی اولیه خودکارها را می بینیم. در اینجا، این توابع نیز به سادگی راه اندازی می شوند، اگرچه خود توابع در فایل های ماژول های آنها نوشته شده است. ببین چقدر راحته متن اصلی برنامه به راحتی قابل خواندن است و با کدهای اضافی که پای شما را می شکند، در هم ریخته نمی شود.

مراحل اولیه به پایان رسیده است، اکنون باید حلقه اصلی را شروع کنیم. برای انجام این کار، پیام شروع را ارسال می کنیم و علاوه بر این، ساعت خود را شروع می کنیم - تایمر را شروع می کنیم.

StartGTimer (TIMER_SEK)؛ //شروع تایمر SendMessage(MSG_LEDON_ACTIVATE); //دستگاه FSM1 را فعال کنید

و حلقه اصلی همانطور که گفتم بسیار ساده به نظر می رسد. ما توابع همه اتومات ها را یادداشت می کنیم، فقط آنها را در یک ستون می نویسیم، بدون اینکه ترتیب را دنبال کنیم. این توابع کنترل کننده های خودکار هستند و در ماژول های خودکار قرار دارند. عملکرد ماژول پردازش پیام این هرم خودکار را تکمیل می کند. البته قبلاً وقتی با سیستم پیام رسان سروکار داشتم این را گفتم. اکنون می توانید ببینید که دو فایل دیگر از ماژول حلقه برنامه اصلی چگونه به نظر می رسند

Hal.h است فایل هدرماژول حلقه اصلی برنامه

#ifndef HAL_h #تعریف HAL_h #شامل #عبارتند از //کتابخانه استاندارد شامل وقفه ها #define LED1 0 #define LED2 1 #define LED3 2 #define LED4 3 #define Komparator ACSR //comparator #define ViklKomparator 1<

همانطور که می بینید، این فایل ذاتا حاوی یک خط کد اجرایی نیست - اینها همه جایگزین های ماکرو و اتصالات کتابخانه هستند. داشتن این فایل فقط زندگی را بسیار خوب می کند، دید را بهبود می بخشد.

اما فایل Hal.c در حال حاضر یک فایل اجرایی است، و همانطور که قبلاً اشاره کردم، حاوی مقداردهی اولیه محیطی مختلف است.

#include "hal.h" void INIT_PEREF(void) (//Initialize I/O Ports //########################### ############################################### # ##### Komparator = ViklKomparator؛ // مقدار دهی اولیه مقایسه کننده - DDRD = 1 را خاموش کنید<

خب ماژول چرخه اصلی برنامه رو نشون دادم حالا باید مرحله آخر رو برداریم باید خود ماژول های اتوماتا رو بنویسیم.

مرحله 3

برای ما باقی می ماند که ماژول های اتوماتای ​​محدود را بنویسیم، در مورد ما، خودکار LEDON و خودکار LEDOFF. برای شروع، متن برنامه را برای دستگاهی که LED را روشن می کند، فایل ledon.c می دهم.

//file ledon.c #include "ledon.h" #include "timers.h" #include "messages.h" char ledon_state بدون علامت; //state متغیر void InitLEDON(void) ( ledon_state=0; //اینجا می توانید سایر متغیرهای خودکار را در صورت وجود مقداردهی اولیه کنید) void ProcessLEDON(void) ( switch(ledon_state) ( case 0: //inactive status if(GetMessage (MSG_LEDON_ACTIVATE )) //اگر پیامی وجود داشته باشد، پذیرفته می شود ( //و تایمر بررسی می شود if(GetGTimer(TIMER_SEK)==one_sek) //اگر تایمر 1s را تنظیم کرده است، سپس اجرا کنید ( StopGTimer(TIMER_SEK); PORTD = 1<

اینجا مثل همیشه در خطوط اول کتابخانه ها به هم وصل شده و متغیرها اعلام می شوند. در مرحله بعد، ما قبلاً توابعی را که قبلاً با آنها ملاقات کرده ایم، رفته ایم. این تابع مقداردهی اولیه خودکار InitLEDON و عملکرد خود کنترل کننده خودکار ProcessLEDON است.

در بدنه کنترل کننده، عملکردهای ماژول تایمر و ماژول پیام در حال حاضر در حال پردازش هستند. و منطق خود اتومات بر اساس طراحی کیس سوئیچ است. و در اینجا می توانید ببینید که کنترل کننده خودکار نیز می تواند با اضافه کردن چند سوئیچ کیس پیچیده شود.

فایل هدر برای خودکار ساده تر خواهد بود:

فایل //fsm1 #ifndef LEDON_h #define LEDON_h #include "hal.h" void InitLEDON(void); void ProcessLEDON(void); #endif

در اینجا فایل لینک hal.h را به هم متصل می کنیم و نمونه اولیه توابع را نیز مشخص می کنیم.

فایلی که مسئول خاموش کردن LED است فقط در تصویر آینه تقریباً یکسان به نظر می رسد ، بنابراین من آن را در اینجا نمایش نمی دهم - عدم تمایل 🙂

شما می توانید تمام فایل های پروژه را از اینجا در این لینک دانلود کنید ====>>> ارتباط دادن.

در اینجا فقط سه مرحله وجود دارد و برنامه ما ظاهری تمام شده پیدا کرده است، به این معنی که ماموریت امروز من به پایان رسیده است و وقت آن است که به پایان برسم. به نظر من اطلاعات ارائه شده در این مقاله برای شما بسیار مفید خواهد بود. اما تنها زمانی سود واقعی را به همراه خواهد داشت که این دانش را عملی کنید.

به هر حال، من تعدادی پروژه جالب برنامه ریزی کرده ام که به خصوص جالب خواهند بود، پس حتماً این کار را انجام دهید برای مقالات جدید مشترک شوید . من همچنین قصد دارم مطالب اضافی را ارسال کنم، بنابراین بسیاری از افراد در حال حاضر از طریق صفحه اصلی سایت مشترک می شوند. اینجا هم می توانید مشترک شوید.

خب، الان واقعا همه چیز دارم، پس برایت آرزوی موفقیت دارم، روحیه خوب و دوباره ببینمت.

N/A ولادیمیر واسیلیف