internet pencereler Android

Sonlu otomat temel kavramları. Sonlu durum makineleri, karışıklık olmadan nasıl programlanır

otomat teorisi

Otomatın tanımı ve çeşitleri. Geçişler ve çıkışların tabloları ve grafikleri. Alt otomatlar. Azaltılmış otomat teoremi

Makinelerle yapılan işlemler

Mealy Makinesini Moore Makinesine ve Moore Makinesini Mealy Makinesine Dönüştürme. Otomatların denkliği. Otomat durumlarının ayırt edilebilirliği. Otomatların minimizasyonu. Otomatların sentezi. Otomatları tanıma

Otomatik - enerji, malzeme, bilgi alma, dönüştürme, aktarma işlemlerinin tamamen otomatikleştirildiği bir mekanizmalar, cihazlar sistemi. "Otomatik" terimi iki açıdan kullanılır:

1) teknik,

2) matematiksel.

Matematiksel yaklaşımda, bir otomat, girdileri, dahili durumları ve çıktıları olması gereken teknik bir cihazın matematiksel modeli olarak anlaşılır. Cihazın yapısının detayları ile ilgili hiçbir bilgi bulunmamalıdır.

Teknik yaklaşımda, bir otomatın çok gerçek bir cihaz olduğu anlaşılmaktadır, örneğin bir telefon kulübesi, bir otomat vb. Bu durumda, elbette, cihazın iç yapısının detayları bilinmektedir.

Bir otomatın özel ve önemli bir durumu, dijital bilgilerin alınması, dönüştürülmesi, depolanması ve yayınlanması süreçlerinin tamamen otomatikleştirildiği bir dijital otomattır (DA).

Sinyaller açısından, CA'yı, giriş sinyallerini onların etkisi altında alabilen, bir durumdan diğerine hareket edebilen, bir sonraki giriş sinyali gelene kadar saklayan ve çıkış sinyalleri veren bir sistem olarak tanımlamak yararlıdır.

X giriş sinyalleri, S durumları ve Y çıkış sinyalleri kümeleri sonluysa CA sonlu olarak kabul edilir Sonlu bir otomat, bilgisayar gibi bir aygıtla ilişkilendirilebilir. Bilgisayar, gelen girdi verilerini çıktı verilerine (sonuç) işler, ancak bu sonuç yalnızca girdi verilerine değil, aynı zamanda bilgisayarın mevcut durumuna, yani. bilgisayarın belleğinde depolanan veriler, örneğin önceki hesaplamaların sonuçları, hesaplama programları.

CA'nın çalışması, giriş sinyallerinin alınma periyotlarının sayısı ile belirlenen otomatik zamanda gerçekleştirilir.

Soyut bir otomat, herhangi bir dilin sembol dizilerini alan bir giriş kanalına, bir çıkış kanalına sahip olan, başka herhangi bir dilin sembol dizilerinin alındığı ve her birinde herhangi bir durumda olan ayrı bir cihazın matematiksel bir modelidir. ayrık zaman anı. Grafiksel olarak, soyut otomat, Şek.

Giriş dilinin sözcükleri, X=(x 1 ,x 2 ,...x n ) olarak adlandırılan kümenin sembolleri ile temsil edilebilir. giriş alfabesi, ve çıkış dilinin sözcükleri Y=(y 1 ,y 2 ,...y p ) kümesinin simgeleridir ve buna Y=(y 1 ,y 2 ,...y p ) denir. çıkış alfabesi. Otomatın durum kümesi S=(s 1 ,s 2 ,...s m ) olarak adlandırılır. devlet alfabesi.


kavram makinenin durumuçıkış sinyalleri yalnızca belirli bir zamanda giriş sinyallerine değil, aynı zamanda bazı tarihöncesine de bağlı olan sistemleri tanımlamak için kullanılır; daha önce sistemin girişlerine gelen sinyaller. Sonuç olarak, dijital otomatlar, daha önce belirtildiği gibi belleğe sahip olan sıralı devrelere aittir. Bir otomatın durumu kavramı, geçmişin bazı anılarına karşılık gelir, bu nedenle bu kavramın tanıtımı açık bir değişken olarak zamanın ortadan kaldırılmasına ve çıkış sinyallerinin durumların ve giriş sinyallerinin bir fonksiyonu olarak ifade edilmesine izin verir.

Soyut bir otomatın çalışması, belirli zaman aralıklarıyla ilgili olarak düşünülmelidir, çünkü her ayrık aralık Tçıktısı y(t) ile eşleşecektir. Sonuç olarak, otomatın işleyişi, sonlu süreli ayrık zaman aralıkları aracılığıyla düşünülür. Sayısal otomatların soyut teorisinde, giriş sinyallerinin her birinin başlangıcında senkron otomat üzerinde hareket ettiği kabul edilir. i Karşılık gelen saat darbesi (döngü) tarafından tahsis edilen zaman aralığı (kuantum) ve otomatın iç durumlarındaki değişiklik, giriş sinyallerinin hiçbir etkisi olmadığında, bitişik saat darbeleri arasındaki zaman aralıklarında meydana gelir.

"Durum" kavramı, otomat tarafından üretilen çıkış dilinin sembollerinin ve/veya kelimelerinin, otomat verilen algoritmayı uyguladığında giriş dilinin sembolleri ve/veya kelimeleri üzerindeki fonksiyonel bağımlılığını oluşturmak için kullanılır. Otomat sОS'nin her durumu için ve ayrık zaman [t] anında her xОX sembolü için, cihazın çıkışında yОY sembolü üretilir. Bu bağımlılık, otomat j'nin çıkış fonksiyonu tarafından belirlenir. Otomat sОS'nin her mevcut durumu ve ayrık zaman [t] anında her xОX sembolü için, otomat bir sonraki sОS durumuna geçer. Bu bağımlılık, otomat y'nin geçiş fonksiyonu tarafından belirlenir. Otomatın işleyişi iki dizinin oluşturulmasından oluşur: otomatın ardışık durumlarının bir dizisi (s 1[ s 2 s 3 ...) ve bir dizi çıkış sembolü (y 1 y 2 y 3 ...), ki bu sembollerin dizisi için (x 1 x 2 x 3 ...) kesikli zaman t = 1,2,3,.... anlarında ortaya çıkar. parantezler - X, Y ve S alfabelerinin sembol dizileri.

Dolayısıyla, sonlu bir otomatın matematiksel modeli, taşıyıcıları üç küme X, Y ve S olan ve işlemler iki işlev j ve y olan üç temel bir cebirdir.

Bu makalede, "durum makinesi" terimi, az sayıdaki durumdan birinde olabilen bir algoritmayı ifade eder. "Durum", giriş ve çıkış sinyallerinin yanı sıra giriş sinyalleri ve sonraki durumların belirli bir ilişkisini tanımlayan belirli bir durumdur. Akıllı okuyucu, bu makalede açıklanan durum makinelerinin Mealy makineleri olduğunu hemen not edecektir. Bir Mealy makinesi, çıktıların yalnızca durumun işlevleri olduğu Moore makinesinin aksine, çıktıların mevcut durumun ve girdinin işlevleri olduğu sonlu durumlu bir makinedir. Her iki durumda da sonraki durum, mevcut durumun ve giriş sinyalinin bir fonksiyonudur.

Basit bir sonlu durum makinesi örneğini düşünün. Bir metin dizisinde "//" karakter dizisini tanımanız gerektiğini hayal edin. Şekil 1, bunun bir durum makinesi kullanılarak nasıl yapıldığını gösterir. Eğik çizginin ilk oluşumu bir çıkış sinyali üretmez, ancak otomatın ikinci duruma girmesine neden olur. Otomat ikinci durumda bir eğik çizgi bulamazsa, arka arkaya 2 eğik çizgiye ihtiyaç duyduğu için ilkine döner. İkinci eğik çizgi bulunursa, otomat bir "hazır" sinyali verir.

Müşterinin neye ihtiyacı olduğunu öğrenin.

Bir Durum Geçiş Şeması çizin

Geçiş işlemlerini detaylandırmadan durum makinesinin "iskeletini" kodlayın.

Geçişlerin düzgün çalıştığından emin olun.

Geçişlerin ayrıntılarını belirtin.

Test yapmak.

Durum makinesi örneği

Sonlu durum makinesinin daha ilginç bir örneğini ele alalım - uçağın iniş takımının geri çekilmesini ve uzatılmasını kontrol eden bir program. Çoğu uçakta bu prosedür bir elektro-hidrolik kontrol mekanizması kullanılarak gerçekleştirilse de (sadece uçakta bilgisayar olmadığı için), insansız hava araçları gibi bazı durumlarda yazılım kontrolünü kullanmaya değer.

İlk önce, ekipmanla ilgilenelim. Uçağın iniş takımı, ön iniş takımı, ana sol iniş takımı ve ana sağ iniş takımından oluşur. Hidrolik bir mekanizma ile tahrik edilirler. Elektrikle çalışan bir hidrolik pompa, güç aktüatörüne basınç sağlar. Yazılım kullanılarak pompa açılır veya kapatılır. Bilgisayar yön valfinin konumunu ayarlar - "aşağı" veya "yukarı", basıncın kasayı yükseltmesine veya indirmesine izin vermek için. İniş takımı ayaklarının her birinin bir limit anahtarı vardır: bunlardan biri iniş takımı kaldırılırsa kapanır, diğeri - "aşağı" konumda sabitlenirse. Uçağın yerde olup olmadığını belirlemek için, uçağın ağırlığı burun dişlisi üzerindeyse, burun dişlisi üzerindeki limit anahtarı kapanır. Pilotun kontrolleri, bir üst/alt iniş takımı kolundan ve kapatılabilen, yeşil (aşağı konum) veya kırmızı (geçiş konumu) üç ışıktan (her bacak için bir tane) oluşur.

Şimdi bir sonlu durum makinesinin geliştirilmesine geçelim. İlk ve en zor adım, müşterinin gerçek beklentilerini anlamaktır. Durum makinesinin avantajlarından biri, programcıyı tüm olası durumları düşünmeye ve bunun sonucunda müşteriden gerekli tüm bilgileri almaya zorlamasıdır. Neden bunu en zor aşama olarak görüyorum? Ve size kaç kez böyle bir görev tanımı verildi: Uçak yerdeyse iniş takımlarını geri çekmeyin.

Tabii ki, bu önemlidir, ancak müşteri bunun her şeyin bittiği yer olduğuna inanır. Peki ya diğer davalar? Uçak yerden kalktığı anda iniş takımlarını geri çekmek yeterli mi? Ya uçak pistteki bir tümseğin üzerinden sekerse? Pilot, park sırasında vites kolunu "yukarı" konuma getirir ve sonuç olarak kalkışa başlarsa ne olur? Bu durumda şasi yükselmeli mi?

Durum makinesi açısından düşünmenin faydalarından biri, müşterinin hemen önünde bir projeksiyon panosuna hızlı bir şekilde bir durum geçiş şeması çizebilmeniz ve süreci onlarla birlikte gerçekleştirebilmenizdir. Durum geçişinin aşağıdaki tanımı kabul edilir: “geçişe neden olan olay” / “geçişin bir sonucu olarak çıkış sinyali”. Yalnızca müşterinin başlangıçta istediği şeyi geliştirseydik (“uçak yerdeyse iniş takımlarını geri çekmeyin”), o zaman Şekil 2'de gösterilen makineyi almış olurduk.

Bir durum geçiş diyagramı (veya başka bir algoritma) hazırlarken aşağıdakileri aklınızda bulundurun:

Bilgisayarlar mekanik donanımlara göre çok hızlıdır.

Yapılması gerekenleri anlatan makine mühendisi, bilgisayarlar ve algoritmalar hakkında sizin kadar bilgi sahibi olmayabilir. Ve bu da olumlu bir nokta, aksi takdirde size ihtiyaç duyulmaz!

Mekanik veya elektrikli bir parça bozulursa programınız nasıl davranır?

Müşterinin gerçekten ne istediğini temel alan bir durum makinesi Şekil 3'te gösterilmektedir. Burada, uçağın iniş takımının kesinlikle havada olana kadar geri çekilmesini önlemek istiyoruz. Bunun için iniş şalterini açtıktan sonra makine birkaç saniye bekler. Ayrıca, uçak park halindeyken birisi kolu hareket ettirirse sorunları önleyecek şekilde, seviyeden ziyade pilot kolunun yükselen kenarına yanıt vermek istiyoruz. İniş takımlarının geri çekilmesi veya uzatılması birkaç saniye sürer ve bu işlem sırasında pilotun fikrini değiştirip kolu ters yönde hareket ettireceği duruma hazırlıklı olmalıyız. Ayrıca, "Kalkış bekleniyor" durumundayken uçak tekrar inerse, zamanlayıcının yeniden başlayacağını ve iniş takımlarının yalnızca uçak 2 saniye boyunca havada kaldığında geri çekileceğini unutmayın.

Durum Makinesi Uygulaması

Liste 1, Şekil 3'te gösterilen durum makinesinin uygulamasıdır. Kodun bazı ayrıntılarını tartışalım.

/*listeleme 1*/

typedef numaralandırma(GEAR_DOWN = 0, WTG_FOR_TKOFF, RAISING_GEAR, GEAR_UP, LOWERING_GEAR) Durum_Türü;

/*Bu dizi, belirli durumlarda çağrılacak işlevlere işaretçiler içerir*/

geçersiz(*state_table)() = (GearDown, WtgForTakeoff, RaisingGear, GearUp, LoweringGear);

State_Type curr_state;

InitializeLdgGearSM();

/* Otomatın kalbi bu sonsuz döngüdür. karşılık gelen fonksiyon

Geçerli durum, yineleme başına bir kez çağrılır */

süre (1) {

durum_tablo();

AzaltmaZamanlayıcı();

geçersiz InitializeLdgGearSM( geçersiz )

curr_state = GEAR_DOWN;

/*Donanımı durdur, ışıkları kapat, vb.*/

geçersiz Vites küçültmek( geçersiz )

/* Uçak varsa bekleme durumuna geç

Yerde değil ve şasiyi kaldırmak için bir komut alındı ​​* /

Eğer((gear_lever == YUKARI) && (prev_gear_lever == AŞAĞI) && (squat_switch == YUKARI)) (

curr_state = WTG_FOR_TKOFF;

prev_gear_lever = gear_lever;

geçersiz RaisingGear( geçersiz )

Eğer((nosegear_is_up == YAPILMIŞ) && (leftgear_is_up == YAPILMIŞ) && (rtgear_is_up == YAPILMIŞ)) (

curr_state = GEAR_UP;

/*Pilot fikrini değiştirdiyse, iniş takımı aşağı durumuna gidin*/

Eğer(gear_lever == AŞAĞI) (

curr_state = LOWERING_GEAR;

geçersiz Vites arttırmak( geçersiz )

/*pilot kolu "aşağı" konuma getirdiyse,

"İniş takımlarını indirme" durumuna git */

Eğer(gear_lever == AŞAĞI) (

curr_state = LOWERING_GEAR;

geçersiz WtgForTakeoff( geçersiz )

/* Kasayı kaldırmadan önce bekleyin. */

Eğer(zamanlayıcı<= 0.0) {

curr_state = RAISING_GEAR;

/*Tekrar dokunursak veya pilot fikrini değiştirirse, her şeye yeniden başlayın*/

Eğer((squat_switch == AŞAĞI) || (gear_lever == AŞAĞI)) (

curr_state = GEAR_DOWN;

/* Kolu tekrar değiştirmesini istemek istemiyorum

Bu sadece bir sıçramaydı.*/

prev_gear_lever = AŞAĞI;

geçersizİndirme Dişlisi( geçersiz )

Eğer(gear_lever == YUKARI) (

curr_state = RAISING_GEAR;

Eğer((nosegear_is_down == YAPILMIŞ) && (leftgear_is_down == YAPILMIŞ) &&(rtgear_is_down == YAPILMIŞ)) (

curr_state = GEAR_DOWN;

İlk olarak, her durumun işlevselliğinin ayrı bir C işlevi tarafından uygulandığını fark edebilirsiniz. Elbette, her durum için ayrı bir durum içeren bir switch deyimi kullanarak bir otomat uygulamak mümkün olabilir, ancak bu çok uzun bir işleve yol açabilir (20-30 durumun her biri için 1 durum başına 10-20 satır kod) ). Testin son aşamalarında kodu değiştirirseniz de hatalara yol açabilir. Bir vakanın sonundaki break ifadesini hiç unutmamış olabilirsiniz ama benim böyle vakalarım oldu. Her durum için ayrı bir fonksiyonunuz varsa, bir devletin kodu asla diğerinin koduna girmeyecektir.

Bir switch ifadesi kullanmaktan kaçınmak için, durum işlevlerine yönelik bir dizi işaretçi kullanıyorum ve dizinin dizini olarak kullanılan değişkeni enum türünde ilan ediyorum.

Basit olması için, anahtarların durumunu okumaktan, pompaları açıp kapatmaktan vb. sorumlu G/Ç donanımı basit değişkenler olarak temsil edilir. Bu değişkenlerin, görünmez yollarla ekipmanla ilişkilendirilen "sihirli adresler" olduğu varsayılır.

Bir başka bariz şey de bu noktada kodun özel bir rol oynamamasıdır. Sadece bir eyaletten diğerine geçer. Bu önemli bir ara adımdır ve göz ardı edilmemelidir. Bu arada, giriş sinyallerinin mevcut durumunu ve değerlerini yazdıracak koşullu derleme yönergeleri (#ifdef DEBUG .. #endif) arasına print ifadeleri eklemek güzel olurdu.

Başarının anahtarı, durum geçişine neden olan kodda, yani. girdinin gerçekleştiğini belirler.

Kod tüm durumlardan doğru bir şekilde geçerse, sonraki adım kodun "doldurulmasını", yani çıkış sinyalini tam olarak neyin ürettiğini yazmaktır. Her geçişin bir giriş sinyali (onu tetikleyen olay) ve bir çıkış sinyali (örneğimizde olduğu gibi donanım G/Ç aygıtı) olduğunu unutmayın. Bunu bir durum geçiş tablosu biçiminde yakalamak genellikle yararlıdır.

Durum geçiş tablosu, durum geçişi başına bir satıra sahiptir.

Bir durum makinesini kodlarken, onu güçlü tutmaya çalışın - müşterinin gereksinimleri ile kod arasında güçlü bir uyum. Örneğin, durum makine kodunun bir durum geçiş tablosu ve bir durum geçiş diyagramı gibi görünmesini sağlamak için donanım ayrıntılarını başka bir işlev katmanında gizlemek gerekebilir. Bu simetri, hataları önlemeye yardımcı olur ve durum makinelerinin gömülü programcı cephaneliğinin neden bu kadar önemli bir parçası olduğunu açıklar. Tabii ki, aynı etkiyi onay kutuları ve sonsuz sayıda iç içe if ifadesiyle elde edebilirsiniz, ancak kodu izlemek ve müşterinin istekleriyle karşılaştırmak çok zor olurdu.

Liste 2'deki kod parçacığı, RaisingGear() işlevini genişletir. RaisingGear() işlevi kodunun Raising Gear durumu için geçiş tablosunun 2 satırını yansıtma eğiliminde olduğunu unutmayın.

geçersiz RaisingGear( geçersiz )

/*Tüm anahtarlar açıldıktan sonra, "şasi yukarı" durumuna gidin*/

Eğer((nosegear_is_up == YAPILMIŞ) && (leftgear_is_up == YAPILMIŞ) && (rtgear_is_up == YAPILMIŞ)) (

pump_motor=KAPALI;

gear_lights = SÖNDÜR;

curr_state = GEAR_UP;

/*Pilot fikrini değiştirirse, iniş takımını geri çekmeye başlayın*/

Eğer(gear_lever == AŞAĞI) (

pump_direction = AŞAĞI;

curr_state = GEAR_LOWERING;

Gizli durumlardan kaçınmayı unutmayın. Gizli durum, tembellikten somut bir durum eklemek yerine koşullu bir alt durum eklemeye çalıştığınızda ortaya çıkar. Örneğin, kodunuz aynı giriş sinyalini moda bağlı olarak farklı şekillerde işliyorsa (yani farklı durum geçişlerini başlatıyorsa), bu bir gizli durumdur. Bu durumda bu devlet ikiye bölünmemeli mi acaba? Gizli durumların kullanılması, durum makinesi kullanmanın tüm faydalarını ortadan kaldırır.

Uygulama olarak, kasanın geri çekme veya uzatma döngüsüne bir zaman aşımı ekleyerek az önce incelediğimiz durum makinesini genişletebilirsiniz. makine mühendisi hidrolik pompanın 60 saniyeden fazla çalışmasını istemiyor. Döngü sona ererse, pilot yeşil ve kırmızı ışıkların yanması ile uyarılmalı ve tekrar denemek için kolu tekrar hareket ettirebilmelidir. Ayrıca varsayımsal bir makine mühendisine, pompa çalışırken ters yönde hareket etmekten nasıl etkilendiğini sorabilirsiniz, çünkü bu 2 kez olur, pilot fikrini değiştirir. Tabii ki, tamirci bunun olumsuz olduğunu söyleyecektir. O zaman yön değiştirirken pompayı hızlı bir şekilde durdurmak için durum makinesini nasıl değiştirirsiniz?

Durum Makinesi Testi

Sonlu durum makineleri olarak kodlama algoritmalarının güzelliği, test planının neredeyse otomatik olarak kendini yazmasıdır. Tek yapmanız gereken her durum geçişinden geçmek. Bunu genellikle elimde bir işaretleyiciyle, testi geçerken durum geçiş diyagramındaki okların üzerini çizerek yaparım. Bu, "gizli durumlardan" kaçınmanın iyi bir yoludur - testlerde belirli durumlardan daha sık gözden kaçırılırlar.

Orta büyüklükteki bir durum makinesinde bile 100'e kadar farklı geçiş olabileceğinden, bu oldukça sabır ve çok kahve gerektirir. Bu arada, atlama sayısı bir sistemin karmaşıklığını ölçmenin harika bir yoludur. İkincisi, müşterinin gereksinimlerine göre belirlenir ve durum makinesi, test kapsamını açık hale getirir. Daha az organize bir yaklaşımla, gereken test miktarı aynı derecede etkileyici olabilir, ancak bunun hakkında hiçbir şey bilmiyorsunuz.

Mevcut durumu, giriş ve çıkış sinyallerinin değerlerini gösteren kodda print deyimlerini kullanmak çok uygundur. Bu, "Yazılım Testinin Altın Kuralı"nın ne ifade ettiğini kolayca gözlemlemenizi sağlar: Programın yapmak istediği şeyi yaptığını ve ayrıca ekstra hiçbir şey yapmadığını test edin. Başka bir deyişle, yalnızca beklediğiniz çıktıları mı alıyorsunuz ve bunun dışında neler oluyor? Herhangi bir "zor" durum geçişi var mı, yani. döngünün sadece bir yinelemesi için rastgele geçen durumlar? Çıktılar, siz onları beklemediğinizde değişiyor mu? İdeal olarak, printfs çıktınızın bir durum geçiş tablosuna çok benzemesi gerekir.

Son olarak - ve bu, yalnızca durum makinesi tabanlı yazılımlar için değil, herhangi bir gömülü yazılım için geçerlidir - yazılımı gerçek donanım üzerinde ilk kez çalıştırırken çok dikkatli olun. Kutupları yanlış anlamak çok kolay - "Hata, '1'in şasiyi yükseltmek ve '0'ın ise düşürmek anlamına geldiğini düşündüm." Çoğu durumda, donanım asistanım, yazılımımın işleri doğru yönde ilerlettiğinden emin olana kadar değerli bileşenleri korumak için geçici bir "tavuk anahtarı" kullandı.

başlatmak

Tüm müşteri gereksinimleri karşılandığında, bu karmaşıklıkta bir durum makinesini birkaç gün içinde başlatabilirim. Neredeyse her zaman otomatlar ne istersem onu ​​yapar. En zoru elbette müşterinin ne istediğini tam olarak anlamak ve müşterinin ne istediğini kendisinin de bildiğinden emin olmaktır. İkincisi çok daha uzun sürer!

Martin Gomez, Johns Hopkins Üniversitesi Uygulamalı Fizik Laboratuvarı'nda programcıdır. Araştırma uzay aracının uçuşu için yazılım geliştirmede görev aldı. 17 yıldır gömülü sistem geliştirme alanında çalışmaktadır. Martin, Cornell Üniversitesi'nden Havacılık ve Uzay Mühendisliği lisans derecesine ve Elektrik Mühendisliği alanında yüksek lisans derecesine sahiptir.

Sonlu bir otomatın karmaşıklığı için kriterlerden biri durumlarının sayısıdır. Bu sayı ne kadar küçükse, bu otomatı uygulayan ayrık cihaz o kadar basit olur. Bu nedenle, sonlu otomata teorisindeki önemli problemlerden biri, en az durumlu bir otomatın inşasıdır.

Modern bilgisayarlarda herhangi bir bilgi ikili kodlar şeklinde temsil edildiğinden, daha sonra bir otomat oluşturmak için, biri 0 sayısına, diğeri 1 sayısına karşılık gelen yalnızca iki farklı kararlı duruma sahip öğeleri kullanabilirsiniz.

İşte bazı sonlu otomat örnekleri.

Örnek 1. Gecikme öğesi (bellek öğesi).

Gecikme elemanları, bir girişi ve bir çıkışı olan bir cihazdır. Ayrıca, o andaki çıkış sinyalinin değeri T önceki andaki sinyal değeriyle çakışır. Şematik olarak gecikme elemanı aşağıdaki gibi gösterilebilir (Şekil 2).

Girişin ve dolayısıyla çıkış alfabesinin x ={0, 1}, Y =(0, 1). O zamanlar Q =(0, 1). O sırada gecikme elemanının durumu altında T Şu anda hafıza elemanının içeriği anlaşılmıştır. Böylece Q (T )= x (T 1), bir Y (T )= Q (T )=x (T 1).

Gecikme öğesini tabloya göre ayarlayalım, burada fakat 1 =0, fakat 2 =1, Q 1 =0, Q 2 =1,

(a 1 , Q 1)= (0, 0)=0, (a 1 , Q 1)= (0, 0)=0;

(a 1 , Q 2)= (0, 1)=0, (a 1 , Q 2)= (0, 1)=1;

(a 2 , Q 1)= (1, 0)=1, (a 2 , Q 1)= (1, 0)=0;

(a 2 , Q 2)= (1, 1)=1, (a 2 , Q 2)= (1, 1)=1;

Q

a

=0, =0

=0, =1

=1, =0

=1, =1

Moore diyagramı şek. 3

Bu otomatı bir Boole fonksiyonları sistemi ile temsil etmek için otomat tablosunu ve yukarıdaki algoritmayı kullanıyoruz. Bu durumda, giriş ve çıkış alfabeleri ve durumları zaten kodlanmış olduğundan kodlama gerekli değildir.

Şu gerçeğe dikkat edelim m=p=p =2. O zamanlar k =r =s =1 ve bu nedenle gecikme elemanı iki fonksiyon tarafından verilir Ve . Bu fonksiyonların doğruluk tablosu 2 tane içerir. k + r =2 2 =4 satır ve k +r +r +s =4 sütun:

x

z

Örnek 2. İkili sıralı toplayıcı.

Bu sıralı toplayıcı, ikili sistemde iki sayı ekleyen bir cihazdır. Toplayıcının girişlerine sayılar beslenir x 1 ve x 2, en az anlamlı rakamlardan başlayarak. Çıkışta, sayı girişine karşılık gelen bir dizi oluşturulur. x 1 +x 2 ikili hesaplama sisteminde (Şekil 4).

Giriş ve çıkış alfabeleri benzersiz bir şekilde tanımlanmıştır: x ={00; 01; 10; 11}, Y =(0,1). Durum kümesi, karşılık gelen sayı bitlerini eklerken transferin değeri ile belirlenir. x 1 ve x 2. Bazı rakamların eklenmesi sırasında bir taşıma oluşursa, toplayıcının duruma geçtiğini varsayacağız. Q 1 . Taşıma yoksa, toplayıcının durumda olduğunu varsayacağız. Q 0 .

Toplayıcı bir tablo tarafından verilir.

Q

a

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

Sıralı bir toplayıcının Moore diyagramı, Şek. beş.

Giriş ve çıkış karakterlerinin zaten kodlanmış olduğunu unutmayın. Durumları şu şekilde kodluyoruz: (Q 0)=0, (Q 1)=1. Bu nedenle, sıralı toplayıcı, doğruluk tablosu aşağıdaki gibi olan iki Boole işlevi tarafından verilir:

x 1

x 2

z

Örnek 3. Eşitlik karşılaştırma şeması.

Eşitlik karşılaştırma devresi, iki sayıyı karşılaştıran bir cihazdır. x 1 ve x 2 , ikili sistemde verilmiştir. Bu cihaz aşağıdaki gibi çalışır. Cihazın girişinde sıralı olarak en yüksekten başlayarak rakamların haneleri beslenir. x 1 ve x 2. Bu sıralar karşılaştırılır. Bitler eşleşirse devrenin çıkışında çıkış sinyali 0 üretilir, aksi halde çıkışta 1 sinyali görünür.Çıkış dizisinde 1'in görünmesi, karşılaştırılan sayıların anlamına geldiği açıktır. x 1 ve x 2 farklı. Çıkış dizisi sıfırsa ve uzunluğu, karşılaştırılan sayıların basamak sayısıyla eşleşiyorsa, o zaman x 1 ve x 2 .

Bu makine için x ={00, 01, 10, 11}; Y ={0,1}.

Devrenin işleyişi iki durum tarafından belirlenir. Belirtmek, bildirmek Q 0 şu anda karşılaştırılan bitlerin eşitliğine karşılık gelir. Bu durumda, makine aynı durumda kalır. Bir sonraki anda karşılaştırılan rakamlar farklıysa, otomat yeni bir duruma geçecektir. Q 1 ve içinde kalır, çünkü sayıların farklı olduğu anlamına gelir. Böylece, karşılaştırma şeması tablo ile belirtilebilir:

Q

x

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

Eşitlik için karşılaştırma şemasının Moore diyagramı, Şek. 6.

Durumları aşağıdaki gibi kodlayacağız: (Q 0)=0, (Q 1)=1. Otomata iki fonksiyon verilecektir.

x 1

x 2

z

Örnek 4. Eşitsizlik karşılaştırma şeması.

Eşitsizlik karşılaştırma devresi, karşılaştırılıp karşılaştırılmadığını öğrenmenizi sağlayan bir cihazdır. x 1 ve x 2 ve eğer eşit değillerse hangisinin diğerinden daha büyük olduğunu bulun. Bu cihazın iki girişi ve iki çıkışı vardır. Çıkış sinyalleri y 1 (T ) Ve y 2 (T ) aşağıdaki kurallara göre belirlenir:

y 1 (T )=y 2 (T )=0 ise x 1 (T )=x 2 (T );

y 1 (T )=1, y 2 (T )=0 ise x 1 (T )>x 2 (T ), yani x 1 (T )=1, x 2 (T )=0;

y 1 (T )=0, y 2 (T )=1 ise x 1 (T )<x 2 (T ), yani x 1 (T )=0, x 2 (T )=1.

Böylece sayı eşitsizliği için karşılaştırma devresinin girişine başvururken x 1 =x 1(l)… x 1 (T ) Ve x 2 =x 2(l)… x 2 (T ) bu sayıların rakamları en büyük olanlardan başlayarak sırayla karşılaştırılır. Çıkış sinyalleri yukarıdaki kurallara göre formüle edilmiştir. Ayrıca, çıkış dizisi sıfır çiftten oluşuyorsa, o zaman x 1 =x 2. İlki sıfırdan farklıysa, çift şu şekildedir: , () sonra x 1 >x 2 (x 1 <x 2).

Devrenin açıklamasından şunu takip eder:

x ={00, 01, 10, 11}, Y ={00, 01, 10}.

Şema durumu aşağıdaki gibi tanımlanır. İlk anda olduğunu varsayıyoruz T =1 makine durumda Q 1 . Rakamların karşılaştırılmış rakamları ise x 1 Ve x 2 eşleşme, ardından makine bu durumda kalır. Çıkışta 00 sinyalinin görüneceğini unutmayın. x 1 sayının karşılık gelen basamağından küçük (büyük) olacaktır x 2, sonra makine duruma girecek Q 2 (Q 3). Bu durumda çıkışta 01 (10) sinyali görünecektir. Gelecekte, sayıların kalan rakamlarını gönderirken x 1 Ve x 2 otomatın girişlerine, otomat durumda kalacaktır Q 2 (Q 3) ve çıkış sembolü 10 (01) oluşturun. Yukarıdakilerden, eşitsizlik için karşılaştırma şemasının tablo tarafından belirtilebileceği sonucuna varılır:

Q

x

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

Karşılık gelen Moore diyagramı Şek. 7.

Giriş ve çıkış alfabeleri zaten burada kodlanmıştır. devletler Q 1 , Q 2 ve Q 3 kodlamak: 1 (Q 1)=00, (Q 2)=01, (Q 3)=10.

Bu nedenle, bu devre, dört değişkene bağlı dört Boole fonksiyonundan oluşan bir sistemle tanımlanabilir. Bu fonksiyonlar kısmen tanımlanmış ve doğruluk tablosu tarafından verilmiştir.

x 1

x 2

z 1

z 2

Tabloda, semboller * değişken kümelerini işaretler x 1 , x 2 , z 1 , z 2 , üzerinde fonksiyonlar 1 , 2 , 1 , 2 tanımlı değil. koyalım fonksiyon değerleri 1 , 2 , 1 , Bu kümelerde 2, 1'e eşittir.

Bugün makineli tüfekler hakkında konuşacağız, ancak hiçbir şekilde Rus ordusunun askerlerinin elinde tutulanlar değil. Otomatik programlama gibi ilginç bir mikrodenetleyici programlama stili hakkında konuşacağız. Daha doğrusu, bu bir programlama tarzı bile değil, bir mikrodenetleyici programcısının hayatını çok daha kolaylaştırabileceği sayesinde bütün bir kavram. Programcıya sunulan birçok görevin çok daha kolay ve basit bir şekilde çözülmesi nedeniyle programcıyı baş ağrısından kurtarır. Bu arada, otomatik programlama genellikle denir SWITCH teknolojisi.

Bu yazıyı yazmanın teşvik edici olduğunu belirtmek isterim. SWITCH teknolojisi hakkında bir dizi makale Vladimir Tatarçevski. Makale dizisine "Mikrodenetleyiciler için uygulama yazılımı geliştirmede SWITCH teknolojisinin uygulanması" denir.

Bu arada, ABP mikrodenetleyicileri için programlama tekniklerini ayrıntılı olarak ele alacağım programlama üzerine bir dizi makale planladım, Kaçırma…. İyi hadi gidelim!

Program, programcı tarafından belirlenen komutları sırayla yürütür. Sıradan bir bilgisayar programı için, programın çalışmasının sonuçlarını monitörde görüntülerken çalışmasını tamamlayıp durdurması tamamen normaldir.

Bir mikrodenetleyici programı basitçe yürütülmesini sonlandıramaz. Oynatıcıyı veya kayıt cihazını açtığınızı hayal edin. Güç düğmesine bastınız, istediğiniz şarkıyı seçtiniz ve müziğin keyfini çıkardınız. Bununla birlikte, müzik kulak zarınızı sallamayı bıraktığında, oynatıcı donar ve düğmelere basmaya hiçbir şekilde tepki vermez ve hatta dahası bir tef ile dans etmenize tepki vermez.

Ve bu nedir? Her şey yolunda - oynatıcınızın derinliklerinde bulunan denetleyici, programını yürütmeyi yeni bitirdi. Burada ne kadar rahatsız edici olduğunu görebilirsiniz.

Dolayısıyla buradan mikrodenetleyici için programın durmaması gerektiği sonucuna varıyoruz. Özünde, sonsuz bir döngü olmalıdır - yalnızca bu durumda oynatıcımız doğru şekilde çalışır. Şimdi size mikrodenetleyiciler için program kodu tasarımlarının neler olduğunu göstereceğim, bunlar tasarım bile değil, bazı programlama stilleri.

Programlama stilleri.

“Programlama stilleri” kulağa biraz anlaşılmaz geliyor, ama neyse. Bununla ne demek istiyorum?Bir kişinin daha önce hiç programlama yapmadığını yani genel olarak tam bir aptal olduğunu düşünelim.

Bu kişi programlama üzerine birçok kitap okudu, dilin tüm temel yapılarını öğrendi.Artık bilgiye erişim sınırsız olduğu için bilgileri parça parça topladı. Bütün bunlar iyi, ama ilk programları nasıl görünecek? Bana öyle geliyor ki felsefe yapmayacak, basitten karmaşığa giden yolu izleyecek.

Dolayısıyla bu stiller, basit bir seviyeden daha karmaşık bir seviyeye giden adımlardır, ancak aynı zamanda daha etkilidir.

İlk başta, programın herhangi bir tasarım özelliğini düşünmedim. Az önce programın mantığını oluşturdum - bir akış şeması çizdim ve kodunu yazdım. Sürekli olarak bir tırmıkla karşılaştığından. Ama ilk defa buhar banyosu yapmadığım ve “basit döngü” stilini kullandığım zaman oldu, sonra kesintileri kullanmaya başladım, sonra otomatlar vardı ve yola çıktık ...

1. Basit döngü. Bu durumda program herhangi bir karmaşıklık olmadan döngü yapar ve bunun artıları ve eksileri vardır. Artı sadece yaklaşımın basitliğinde, kurnaz tasarımlar icat etmenize gerek yok, düşündüğünüz gibi yazıyorsunuz (yavaş yavaş kendi mezarınızı kazıyorsunuz).

Void main(void) ( initial_AL(); //çevrenin başlatılması while(1) ( Leds_BLINK(); //LED flaşör sinyali_on(); //sinyali açma işlevi signal_off(); // sinyali kapatma işlevi l=button(); //butonlara basmaktan sorumlu değişken switch(l) //Değişkenin değerine bağlı olarak, bir veya başka bir işlem gerçekleştirilir ( durum 1: ( Deistvie1(); / /Bir fonksiyon yerine, koşullu bir operatör Deistvie2(); //veya birkaç dal geçiş durumu Deistvie3(); Deistvie4(); Deistvie5(); ); durum 2: ( Deistvie6(); Deistvie7(); Deistvie8(); Deistvie9(); Deistvie10(); ); . . . . . . ) ) )

Programın çalışma noktası sırayla hareket eder. Bu durumda, tüm eylemler, koşullar ve döngüler sırayla yürütülür. Kod yavaşlamaya başlar, birçok ekstra koşul eklemeniz gerekir, bu da algıyı karmaşıklaştırır.

Bütün bunlar, programı büyük ölçüde karıştırır ve koddan bir dizi koşul çıkarır. Sonuç olarak, bu kod eklenemez veya çıkarılamaz, yekpare bir parça gibi olur. Tabii ki, hacim büyük olmadığında kod değiştirilebilir, ancak daha da zorlaşır.

Bu yaklaşımla birkaç program yazdım, büyük değillerdi ve pek işe yaramadılar, ancak görünürlük arzulananı bıraktı. Yeni bir koşul eklemek için, tüm kodu kürek çekmem gerekti, çünkü her şey bağlıydı. Bu, birçok hataya ve baş ağrısına neden oldu. Derleyici elinden geldiğince çabuk yemin etti, böyle bir programın hatalarını ayıklamak cehenneme döndü.

2. Döngü + kesintiler.

Kesintileri kullanarak sonsuz frenleme döngüsünü kısmen çözebilirsiniz. Kesintiler, bir kısır döngüden kurtulmaya, önemli bir olayı kaçırmamaya, ek işlevler eklemeye yardımcı olur (zamanlayıcılardan gelen kesintiler, harici kesintiler).

Diyelim ki düğmelerin işlenmesini veya bir kesintide önemli bir olayı izlemeyi kapatabilirsiniz. Sonuç olarak, program daha görsel hale gelir ancak daha az kafa karıştırıcı olmaz.

Ne yazık ki, kesinti sizi programın dönüştüğü karmaşadan kurtarmaz. Tek bir bütünü parçalara bölmek mümkün olmayacaktır.

3. Otomatik programlama.

Böylece bu makalenin ana konusuna geliyoruz. Sonlu durum makinelerinde programlama, programı ilk iki örnekte bulunan eksikliklerden kurtarır. Program daha basit hale gelir, değiştirilmesi kolaydır.

Otomat tarzında yazılmış bir program, koşullara bağlı olarak şu veya bu duruma geçiş yapan bir anahtar gibidir. Durumların sayısı başlangıçta programcı tarafından bilinir.

Kabaca, bir ışık anahtarı gibi. Açık ve kapalı iki durum ve açık ve kapalı iki durum vardır. İlk önce ilk şeyler.

Anahtar teknolojisinde çoklu görev uygulaması.

Mikrodenetleyici yükü kontrol edebilir, LED'leri yanıp sönebilir, tuş vuruşlarını izleyebilir ve çok daha fazlasını yapabilir. Ama tüm bunlar aynı anda nasıl yapılır? Bu sorunun birçok çözümü var. Bunlardan en basiti daha önce bahsettiğim kesintilerin kullanılmasıdır.

Program sırasında, bir kesinti meydana geldiğinde, denetleyicinin dikkati program kodunun yürütülmesinden uzaklaştırılır ve kısa süreliğine, kesintinin sorumlu olduğu programın başka bir parçasını yürütür. Kesme çalışacaktır, ardından programın çalışma noktası, kesme tarafından kontrolörün kesintiye uğratıldığı yerden devam edecektir (kelimenin kendisi, kontrolörün kesintiye uğradığını gösterir).

Çoklu görevi uygulamanın başka bir yolu da işletim sistemlerinin kullanılmasıdır. Evet, düşük güçlü bir denetleyicide kullanılabilecek küçük işletim sistemleri zaten görünmeye başladı. Ancak çoğu zaman bu yöntemin biraz gereksiz olduğu ortaya çıkıyor. Sonuçta, az kan dökülmesiyle geçinmek oldukça mümkünken, neden kontrolör kaynaklarını gereksiz çalışmalarla boşa harcıyorsunuz?

Anahtar teknolojisi kullanılarak yazılan programlarda, mesajlaşma sistemi sayesinde böyle bir çoklu görev “illüzyonu” elde edilir. "İllüzyon" yazdım çünkü gerçekten öyle çünkü program fiziksel olarak kodun farklı bölümlerini aynı anda çalıştıramıyor. Mesajlaşma sisteminden biraz daha bahsedeceğim.

Mesajlaşma sistemi.

Mesajlaşma sistemini kullanarak birden fazla işlemi yok edebilir ve çoklu görev yanılsaması yaratabilirsiniz.

Diyelim ki LED'in değiştirildiği bir programa ihtiyacımız var. Burada iki makinemiz var, onlara LEDON diyelim - LED'i açmaktan sorumlu makine ve LEDOFF makinesi - LED'i kapatmaktan sorumlu makine.

Otomatların her birinin iki durumu vardır, yani otomat, bir bıçak anahtarının açık veya kapalı olması gibi aktif durumda veya etkin olmayan bir durumda olabilir.

Bir makine etkinleştirildiğinde LED yanar, diğer makine etkinleştirildiğinde LED söner. Küçük bir örnek düşünün:

Int main(void) ( INIT_PEREF(); //çevre birimlerinin (LED'ler) başlatılması InitGTimers(); //zamanlayıcıların başlatılması InitMessages(); //mesaj işleme mekanizmasının başlatılması InitLEDON(); //LEDON otomatının başlatılması InitLEDOFF(); // LEDOFF otomatının başlatılması SendMessage(MSG_LEDON_ACTIVATE); //LEDON otomatını etkinleştir sei(); //Kesmeleri etkinleştir //Ana döngü while(1) ( ProcessLEDON(); //LEDON yinelemesi otomat ProcessLEDOFF(); //LEDOFF otomatının ProcessMessages yinelemesi (); //mesaj işleme ); )

3-7. satırlarda, çeşitli başlatmalar meydana gelir, dolayısıyla şu anda bununla özellikle ilgilenmiyoruz. Ama sonra şu olur: ana döngüye başlamadan önce (((((()))) bir mesaj göndeririz, otomata bir mesaj göndeririz.

SendMessage(MSG_LEDON_ACTIVATE)

LED'i aydınlatmaktan sorumludur. Bu küçük adım olmadan, hurdy-gurdy çalışmaz. Ardından, işin büyük kısmını ana sonsuz while döngüsü yapar.

Küçük arasöz:

Mesajın üç durumu vardır. Yani, mesaj durumu inaktif, ayarlanmış fakat inaktif ve aktif olabilir.

Mesajın başlangıçta etkin olmadığı ortaya çıktı, mesajı gönderdiğimizde "yüklü ancak etkin değil" durumunu aldı. Ve bu bize aşağıdakileri verir. Program sıralı olarak yürütüldüğünde LEDON otomatı bir mesaj almaz. LEDON otomatının boşta bir yinelemesi, mesajın basitçe alınamadığı gerçekleşir. Mesaj "kurulu ancak etkin değil" durumuna sahip olduğundan, program yürütmeye devam eder.

Tüm otomatlar boşta kaldıktan sonra kuyruk ProcessMessages() işlevine ulaşır. Bu işlev, tüm otomat yinelemeleri tamamlandıktan sonra her zaman döngünün sonuna yerleştirilir. ProcessMessages() işlevi, mesajı "ayarlanmış ancak etkin değil" durumundan "etkin" duruma değiştirir.

Sonsuz döngü ikinci turu tamamladığında, resim zaten tamamen farklıdır. ProcessLEDON otomatının yinelemesi artık boşta olmayacak. Makine mesajı alabilecek, yanan duruma geçebilecek ve ayrıca mesajı sırayla gönderebilecektir. LEDOFF otomatına yönlendirilecek ve mesajın yaşam döngüsü tekrarlanacaktır.

"Aktif" duruma sahip olan mesajların ProcessMessages işlevi ile karşılaştıklarında yok edildiğini belirtmek isterim. Bu nedenle, bir mesaj sadece bir otomat tarafından alınabilir. Başka bir mesaj türü daha var - bunlar yayın mesajları, ama onları dikkate almayacağım, onlar da Tatarchevskiy'in makalelerinde iyi bir şekilde ele alındı.

zamanlayıcılar

Doğru mesajlaşma ile durum makinelerinin çalışma sırasını kontrol edebiliriz, ancak sadece mesajlar olmadan yapamayız.

Önceki örnek kod parçacığının istendiği gibi çalışmayacağını fark etmiş olabilirsiniz. Makineler mesaj alışverişi yapacak, LED'ler değişecek ama biz bunu görmeyeceğiz. Sadece loş bir LED göreceğiz.

Bunun nedeni, gecikmelerin yetkili bir şekilde işlenmesini düşünmememizdir. Sonuçta, LED'leri dönüşümlü olarak açıp kapatmak bizim için yeterli değil, LED'in her durumda, örneğin bir saniye oyalanması gerekir.

Algoritma aşağıdaki gibi olacaktır:

Büyütmek için tıklayabilirsiniz

Bu blok şemaya, zamanlayıcı işaretlendiğinde, elbette bir eylemin gerçekleştirildiğini - LED'i yakmayı veya kapatmayı eklemeyi unuttum.

1. Duruma mesaj alarak giriyoruz.

2. Zamanlayıcı/sayaç okumalarını kontrol ediyoruz, işaretli ise işlemi gerçekleştiriyoruz, yoksa kendimize mesaj gönderiyoruz.

3. Bir sonraki otomata bir mesaj gönderiyoruz.

4. Çıkış

Bir sonraki girişte, her şey tekrarlanır.

SWITCH teknolojisi programı. Üç adım.

Ve hadi sonlu otomatlarda bir program yazalım ve bunun için sadece üç basit adım yapmamız gerekecek. Program basit olacak, ancak basit şeylerle başlamaya değer. Anahtarlama LED'li bir program bizim için uygundur. Bu çok iyi bir örnek, o yüzden yeni bir şey icat etmeyelim.

Programı C dilinde yazacağım, ancak bu, sonlu otomatalarda sadece C ile yazmanız gerektiği anlamına gelmiyor, başka herhangi bir programlama dili kullanmak oldukça mümkün.

Program modüler olacak ve bu nedenle birkaç dosyaya bölünecek. Modüllerimiz şunlar olacak:

  • Programın ana döngü modülü leds_blink.c, HAL.c, HAL.h dosyalarını içerir.
  • Zamanlayıcı modülü timers.c, timers.h dosyalarını içerir
  • Mesaj işleme modülü Mesajlar.c, Mesajlar.h dosyalarını içerir
  • Makine modülü 1 ledon.c, ledon.h dosyalarını içerir
  • Makine modülü 2 ledoff.c dosyalarını içerir, ledoff .h

Aşama 1.

Bir proje oluşturuyoruz ve statik modüllerimizin dosyalarını hemen ona bağlıyoruz: timers.c, timers.h, message.c, message.h.

Programın ana döngüsünün modülünün leds_blink.c dosyası.

#include "hal.h" #include "messages.h" //mesaj işleme modülü #include "timers.h" //timer modülü //Zamanlayıcı kesintileri //############# # ################################################# ############################# ISR(TIMER0_OVF_vect) // Kesinti vektör geçişi (T0 sayaç zamanlayıcı taşması) ( ProcessTimers(); / /Zamanlayıcı kesme işleyicisi) //###################################### ### ################################################ int ana (void) ( INIT_PEREF(); //çevrenin başlatılması (LED'ler) InitGTimers(); //zamanlayıcıların başlatılması InitMessages(); //mesaj işleme mekanizmasının başlatılması InitLEDON(); //LEDON otomatının başlatılması InitLEDOFF(); StartGTimer( TIMER_SEK); //Zamanlayıcıyı başlat SendMessage(MSG_LEDON_ACTIVATE); //FSM1 otomatını etkinleştir sei(); //Kesmeleri etkinleştir //Ana döngü while(1) ( ProcessLEDON(); //yineleme) LEDON otomatı ProcessLEDOFF(); ProcessMessages(); //mesaj işleme ); )

İlk satırlarda kalan modüller ana programa bağlanır. Burada timer modülü ile mesaj işleme modülünün bağlı olduğunu görüyoruz. Programda sonraki, taşma kesme vektörüdür.

int main (void) satırından ana programın başladığını söyleyebiliriz. Ve her şeyin ve her şeyin başlatılmasıyla başlar. Burada çevre birimlerini başlatıyoruz, yani ilk değerleri karşılaştırıcının giriş / çıkış portlarına ve kontrolörün diğer tüm içeriğine ayarlıyoruz. Bütün bunlar INIT_PEREF işlevi tarafından yapılır, ana gövdesi hal.c dosyasında olmasına rağmen burada çalıştırıyoruz.

Ardından, zamanlayıcıların başlatılmasını, mesaj işleme modülünü ve otomatların başlatılmasını görüyoruz. Burada, işlevlerin kendileri modüllerinin dosyalarına yazılmasına rağmen, bu işlevler de basitçe başlatılır. Ne kadar uygun olduğunu görün. Programın ana metninin okunması kolay kalır ve bacağınızı kıran gereksiz kodlarla karıştırılmaz.

Ana başlatmalar bitti, şimdi ana döngüyü başlatmamız gerekiyor. Bunu yapmak için başlangıç ​​mesajını gönderiyoruz ve ayrıca saatimizi başlatıyoruz - zamanlayıcıyı başlatıyoruz.

BaşlatGTimer(TIMER_SEK); //zamanlayıcıyı başlat SendMessage(MSG_LEDON_ACTIVATE); //FSM1 makinesini etkinleştir

Ve ana döngü, dediğim gibi, çok basit görünüyor. Tüm otomatların fonksiyonlarını yazıyoruz, sırayı takip etmeden sadece bir sütuna yazıyoruz. Bu işlevler otomat işleyicileridir ve otomat modüllerinde bulunur. Mesaj işleme modülünün işlevi bu otomat piramidini tamamlar. Tabii bunu daha önce mesajlaşma sistemiyle uğraşırken söylemiştim. Artık ana program döngüsünün modülünün iki dosyasının daha nasıl göründüğünü görebilirsiniz.

Hal.h, programın ana döngü modülünün başlık dosyasıdır.

#ifndef HAL_h #define HAL_h #include #Dahil etmek //Kesintileri içeren standart kütüphane #define LED1 0 #define LED2 1 #define LED3 2 #define LED4 3 #define Komparator ACSR //karşılaştırıcı #define ViklKomparator 1<

Gördüğünüz gibi, bu dosya doğası gereği tek bir yürütülebilir kod satırı içermez - bunların tümü makro ikameleri ve kitaplık bağlantılarıdır. Bu dosyaya sahip olmak hayatı çok güzel kılıyor, görünürlüğü artırıyor.

Ancak Hal.c dosyası zaten yürütülebilir bir dosyadır ve daha önce de belirttiğim gibi çeşitli çevresel başlatmalar içerir.

#include "hal.h" void INIT_PEREF(void) ( //G/Ç bağlantı noktalarını başlat //########################### ################################################# # ##### Komparator = ViklKomparator; //karşılaştırıcı başlatma - DDRD'yi kapatın = 1<

Eh, programın ana döngüsünün modülünü gösterdim, şimdi son adımı atmamız gerekiyor, otomatların modüllerini kendimiz yazmamız gerekiyor.

Aşama 3

Bizim durumumuzda LEDON otomatı ve LEDOFF otomatı olan sonlu otomatların modüllerini yazmak bize kalıyor. Başlangıç ​​olarak LED'i yakan makine için programın metnini, ledon.c dosyasını vereceğim.

//dosya ledon.c #include "ledon.h" #include "timers.h" #include "messages.h" unsigned char ledon_state; //durum değişkeni void InitLEDON(void) ( ledon_state=0; //varsa burada diğer otomat değişkenlerini başlatabilirsiniz) void ProcessLEDON(void) ( switch(ledon_state) ( case 0: //inactive state if(GetMessage (MSG_LEDON_ACTIVATE) )) //bir mesaj varsa kabul edilecektir ( //ve timer kontrol edilecektir if(GetGTimer(TIMER_SEK)==one_sek) //zamanlayıcı 1s ayarlamışsa, çalıştır ( StopGTimer(TIMER_SEK); PORTD = 1<

Burada ilk satırlarda her zaman olduğu gibi kütüphaneler bağlanır ve değişkenler bildirilir. Ardından, daha önce tanıştığımız işlevlere zaten gittik. Bu, InitLEDON otomatının başlatma işlevi ve ProcessLEDON otomat işleyicisinin kendisinin işlevidir.

İşleyicinin gövdesinde, zamanlayıcı modülünden ve mesaj modülünden gelen işlevler halihazırda işlenmektedir. Ve otomatın mantığı, anahtar kasası tasarımına dayanmaktadır. Ve burada, birkaç durum anahtarı ekleyerek otomat işleyicinin de karmaşık olabileceğini görebilirsiniz.

Otomatın başlık dosyası daha da basit olurdu:

//fsm1 dosyası #ifndef LEDON_h #define LEDON_h #include "hal.h" void InitLEDON(void); void ProcessLEDON(void); #endif

Burada hal.h link dosyasını bağlarız ve ayrıca fonksiyonların prototiplerini belirtiriz.

LED'i kapatmaktan sorumlu dosya yalnızca ayna görüntüsünde neredeyse aynı görünecek, bu yüzden burada göstermeyeceğim - isteksizlik 🙂

Tüm proje dosyalarını bu linkten indirebilirsiniz ====>>> BAĞLANTI.

İşte sadece üç adım ve programımız bitmiş bir görünüme kavuştu, bu da bugünkü görevimin bittiği ve tamamlamanın zamanı geldiği anlamına geliyor. Bana öyle geliyor ki bu yazıda verilen bilgiler sizin için çok faydalı olacak. Ancak ancak bu bilgiyi uygulamaya koyduğunuzda gerçek fayda sağlayacaktır.

Bu arada, özellikle ilginç olacak bir dizi ilginç proje planladım, bu yüzden emin olun. yeni makaleler için abone olun . Ayrıca ek materyaller göndermeyi planlıyorum, pek çok kişi sitenin ana sayfasından abone oluyor. Buradan da abone olabilirsiniz.

Pekala, şimdi gerçekten her şeye sahibim, bu yüzden size iyi şanslar, iyi bir ruh hali ve tekrar görüşmek dileğiyle.

Yok Vladimir Vasiliev

Durum makinesi, bir şeyin sonlu sayıda durumunu içeren soyut bir modeldir. Herhangi bir komutun yürütme akışını temsil etmek ve kontrol etmek için kullanılır. Durum makinesi, oyunlarda yapay zeka uygulamak, hantal ve karmaşık kodlar yazmadan düzgün bir çözüm elde etmek için idealdir. Bu yazıda, teoriyi ele alacağız ve ayrıca basit ve yığın tabanlı bir durum makinesinin nasıl kullanılacağını öğreneceğiz.

Halihazırda bir durum makinesi kullanarak yapay zeka yazmak üzerine bir dizi makale yayınladık. Bu seriyi henüz okumadıysanız, şimdi okuyabilirsiniz:

Sonlu durum makinesi nedir?

Bir sonlu durum makinesi (veya basitçe FSM - Sonlu durum makinesi), varsayımsal bir durum makinesine dayanan bir hesaplama modelidir. Aynı anda sadece bir durum aktif olabilir. Bu nedenle, herhangi bir eylemi gerçekleştirmek için makinenin durumunu değiştirmesi gerekir.

Durum makineleri genellikle bir şeyin yürütme akışını düzenlemek ve temsil etmek için kullanılır. Bu, özellikle oyunlarda AI uygularken kullanışlıdır. Örneğin, düşmanın "beynini" yazmak için: her durum bir tür eylemi temsil eder (saldırı, kaçma vb.).

Sonlu bir otomat, köşeleri durumlar ve kenarları aralarındaki geçişler olan bir grafik olarak temsil edilebilir. Her kenar, geçişin ne zaman gerçekleşmesi gerektiğini gösteren bir etikete sahiptir. Örneğin, yukarıdaki resimde, oyuncunun yakınlarda olması koşuluyla, otomatın durumu "gezinme" durumundan "saldırı" durumuna değiştireceğini görebilirsiniz.

Zamanlama durumları ve geçişleri

Bir sonlu durum makinesinin uygulanması, durumlarının ve aralarındaki geçişlerin tanımlanmasıyla başlar. Bir karınca yuvasına yaprak taşıyan bir karıncanın hareketlerini tanımlayan bir durum makinesi düşünün:

Başlangıç ​​noktası, karınca yaprağı bulana kadar aktif kalan "yaprak bul" durumudur. Bu olduğunda, devlet "eve git" olarak değişecektir. Karıncamız karınca yuvasına gelene kadar aynı durum aktif kalacaktır. Bundan sonra durum tekrar "yaprak bul" olarak değişir.

"Yaprak bul" durumu etkinse, ancak fare imleci karıncanın yanındaysa, durum "kaçmak" olarak değişir. Karınca, fare imlecinden yeterince güvenli bir mesafeye gelir gelmez, durum tekrar "yaprak bul" olarak değişecektir.

Karıncanın eve giderken veya evden çıkarken fare imlecinden korkmayacağını lütfen unutmayın. Niye ya? Ve karşılık gelen bir geçiş olmadığı için.

Basit bir durum makinesinin uygulanması

Bir durum makinesi, tek bir sınıf kullanılarak uygulanabilir. FSM diyelim. Fikir, her durumu bir yöntem veya işlev olarak uygulamaktır. Aktif durumu belirlemek için activeState özelliğini de kullanacağız.

Genel sınıf FSM ( private var activeState:Function; // otomatın aktif durumuna işaretçi genel fonksiyon FSM() ( ) genel fonksiyon setState(state:Function) :void ( activeState = durum; ) public function update() :void ( if ( activeState != null) ( activeState(); ) ) )

Her durum bir fonksiyondur. Üstelik oyun çerçevesi her güncellendiğinde çağrılacak şekilde. Daha önce de belirtildiği gibi, activeState, aktif durum işlevine bir işaretçi depolayacaktır.

FSM sınıfının update() yöntemi her oyun karesinde çağrılmalıdır. Ve sırayla, şu anda aktif olan devletin işlevini arayacak.

setState() yöntemi, yeni etkin durumu ayarlayacaktır. Ayrıca, otomatın bazı durumlarını tanımlayan her işlevin FSM sınıfına ait olması gerekmez - bu, sınıfımızı daha evrensel hale getirir.

Durum makinesi kullanma

Karınca AI'yı uygulayalım. Yukarıda, hallerini ve aralarındaki geçişleri zaten gösterdik. Onları tekrar gösterelim, ama bu sefer koda odaklanacağız.

Karıncamız, beyin alanı olan Ant sınıfı ile temsil edilmektedir. Bu sadece FSM sınıfının bir örneğidir.

Genel sınıf Ant ( genel var konum:Vector3D; genel var hız:Vector3D; genel var beyin:FSM; genel işlev Ant(posX:Number, posY:Number) ( konum = yeni Vector3D(posX, posY); hız = yeni Vector3D( -1, -1); beyin = new FSM(); // Bir yaprak arayarak başlayın. beyin. setState(findLeaf); ) /** * "findLeaf" durumu. * Karıncanın yaprak aramasını sağlar. */ public function findLeaf() :void ( ) /** * "goHome" durumu * Karıncanın karınca yuvasına girmesine neden olur */ public function goHome() :void ( ) /** * "runAway" durumu * * / public function runAway() :void ( ) public function update():void ( // Durum makinesini güncelleyin. Bu işlev // aktif durum işlevini çağırır: findLeaf(), goHome () veya runAway(). brain.update(); // Karıncanın hareketine hız uygulayın. moveBasedOnVelocity(); ) (...) )

Ant sınıfı ayrıca hız ve konum özelliklerini de içerir. Bu değişkenler, Euler yöntemini kullanarak hareketi hesaplamak için kullanılacaktır. Oyun çerçevesi her güncellendiğinde update() işlevi çağrılır.

Aşağıda, yaprakları bulmaktan sorumlu durum olan findLeaf() ile başlayarak, yöntemlerin her birinin uygulaması yer almaktadır.

Genel işlev findLeaf() :void ( // Karıncayı yaprağa taşır. hız = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y); if (mesafe (Oyun .instance.leaf, bu)<= 10) { // Муравей только что подобрал листок, время // возвращаться домой! brain.setState(goHome); } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши находится рядом. Бежим! // Меняем состояние автомата на runAway() brain.setState(runAway); } }

goHome() durumu, karıncanın eve gitmesini sağlamak için kullanılır.

Genel işlev goHome() :void ( // Karıncayı ev hızına taşır = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y); if (distance( Oyun. instance.home, bu)<= 10) { // Муравей уже дома. Пора искать новый лист. brain.setState(findLeaf); } }

Ve son olarak, runAway() durumu - fare imlecinden kaçarken kullanılır.

Genel işlev runAway() :void ( // Karıncayı imleç hızından uzaklaştırır = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // İmleç hala etrafta mı? ? if ( Distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) ( // Hayır, zaten çok uzakta. Yaprakları bulmaya geri dönme zamanı. brain.setState(findLeaf); ) )

FSM İyileştirmesi: Yığın Tabanlı Otomat

Eve giderken karıncanın da fare imlecinden kaçması gerektiğini hayal edin. FSM eyaletleri şöyle görünecek:

Önemsiz bir değişiklik gibi görünüyor. Hayır, böyle bir değişiklik bizim için sorun yaratır. Mevcut durumun "kaçmak" olduğunu hayal edin. Fare imleci karıncadan uzaklaşırsa ne yapmalı: eve mi gitsin yoksa yaprak mı ara?

Bu sorunun çözümü yığın tabanlı bir durum makinesidir. Yukarıda uyguladığımız basit FSM'den farklı olarak, bu tür FSM, durumları yönetmek için bir yığın kullanır. Yığının en üstünde aktif durum bulunur ve yığından durumlar eklendiğinde/çıkarıldığında geçişler meydana gelir.

Ve işte yığına dayalı bir durum makinesinin çalışmasının görsel bir gösterimi:

Yığın tabanlı bir FSM'nin uygulanması

Böyle bir sonlu durum makinesi, basit bir makineyle aynı şekilde uygulanabilir. Fark, gerekli durumlara yönelik bir dizi işaretçinin kullanılması olacaktır. Artık activeState özelliğine ihtiyacımız olmayacak, çünkü yığının üst kısmı zaten aktif duruma işaret edecektir.

Genel sınıf StackFSM ( private var stack:Array; public function StackFSM() ( this.stack = new Array(); ) public function update() :void ( var currentStateFunction:Function = getCurrentState(); if (currentStateFunction != null) ( currentStateFunction(); ) ) genel işlev popState() :Function ( dönüş stack.pop(); ) genel işlev pushState(durum:Function) :void ( if (getCurrentState() != durum) ( stack.push(durum) ; ) ) genel işlev getCurrentState() :Function ( dönüş yığın.uzunluk > 0 ? yığın: boş; ) )

setState() yönteminin pushState() (yığının en üstüne yeni durum eklenmesi) ve popState() (yığının tepesinden durumu kaldırma) ile değiştirildiğini unutmayın.

Yığın tabanlı FSM'yi kullanma

Yığın tabanlı bir durum makinesi kullanırken, her durumun gerekmediğinde kendisini yığından çıkarmaktan sorumlu olduğuna dikkat etmek önemlidir. Örneğin, düşman zaten yok edilmişse, attack() durumu kendisini yığından kaldırmalıdır.

Public class Ant ( (...) public var brain:StackFSM; public function Ant(posX:Number, posY:Number) ( (...) brain = new StackFSM(); // Bir yaprak beyin arayarak başlayın. pushState( findLeaf); (...) ) /** * "findLeaf" durumu * Karıncanın yaprakları aramasını sağlar */ public function findLeaf() :void ( // Karıncayı yaprağa taşır. hız = yeni Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y); if (mesafe(Game.instance.leaf, bu)<= 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) ( // Hayır, zaten çok uzakta. Yaprak aramaya geri dönmenin zamanı geldi. brain.popState(); ) ) (...) )

Çıktı

Durum makineleri, oyunlarda yapay zeka mantığını uygulamak için kesinlikle faydalıdır. Geliştiricinin olası tüm seçenekleri görmesini sağlayan bir grafik olarak kolayca temsil edilebilirler.

Durum işlevlerine sahip bir durum makinesi uygulamak, basit ama güçlü bir tekniktir. FSM kullanılarak daha da karmaşık durum karışıklıkları uygulanabilir.