RS485 donanımı ucuz, uygulaması kolay bir haberleşme arayüzü olarak öncelikli tercihlerimizden biri olmayı sürdürmektedir. Endüstriyel otomasyon ve veri toplama gibi işlerde eskilerden beri kullanılagelen Modbus namlı bir protokol vardır. Bu protokol çok basit, çok minimalist bir kullanım ile RS485 üzerinden multi-drop, yani ikiden fazla cihazın müşterek kullandığı bir haberleşme ortamı kurmaya iyi bir örnektir.
Yakın zamanda benden Modbus üzerinden kumanda edilen bir takım giriş/çıkış modülleri tasarlamam istendi. Bu vesileyle de, uzun yıllardır pek çok farklı işte kullanmakta olduğum Modbus protokol yürütmesi hakkında birkaç not yazayım diye düşündüm.
Burada, Modbus protokolünün embedded tarafta, kaynakları sınırlı işlemciler üzerinde yürütülmesine dair yaptığım bazı şeyleri paylaşacağım. PC tarafında da kendi yazdığım modbus erişim programlarına örnekler vereceğim.
MODBUS RTU
Modbus protokolünün RS485 hattı üzerinde çalışan RTU yürütmesini kullanıyorum. Bu yürütmede paket byte’ları binary olarak anlamlanıyor.
Protokol byte formatı olarak 8-E-1 kullanılır diyor. Ancak ben 8-N-1 kullanıyorum. (Hâlâ, donanımsal parite hesabı yapmayan işlemciler kullanan insanlar var çünkü) Aslında protokol, eğer parite kullanamıyorsan bari stop bitini 2 tane yap da diyor ama ben bunu da yapmadım.
Modbus’un standart protokol tanımlamalarına “olabildiğince” sadık kalmaya çalıştım. Aslında her ne kadar yalınlığına saygı duysam da, RS485 multi-drop pek çok uygulamada modbus en etkin yöntem olmuyor. Eğer sizin tasarladığınız cihazların dışında cihazlarla da haberleşecek bir ağ öngörüyorsanız o zaman işlerin bir standardı olmalı dediğiniz için modbus’ı ciddiye alırsınız. Belki bir gün RS485 üzerinde daha efektif veri haberleşmesi yapan kendi yöntemlerimi de burada paylaşmaya başlarım.
MODBUS ZAMANLAMALARI
RS485 asenkron bir seri haberleşme ortamıdır. Bu da demektir ki bu ortamda birbiriyle haberleşecek cihazlar yollanan verilerin içeriği kadar zamanlaması üzerinde de anlaşmış olmalıdırlar.
Modbus spesifikasyonu, iki byte arasında en fazla 1,5 byte süresi kadarlık bir boşluğa izin verildiğini belirtiyor. Bundan çıkaracağımız iki sonuç vardır:
- Paket byte’larını bekleme yapmadan art arda yollamamız gerekir.
- Bir veri alıyorken, 1,5 byte süresi kadar alma yapmazsak paket sonuna geldik diyebiliriz.
Bir alma işlemi esnasında paket boyunu saymak zorunda kalmadan paket almanın sonuna geldiğimize karar verebilmek işleri kolaylaştıran bir şeydir. Öyle olmasaydı, modbus paket boyları sabit uzunlukta olmadıkları için, bir veri gelmeye başladığında doğru anda paket boyunun ne olacağını hesaplamış olmamız gerekirdi. Ki bunun için de gelen verinin kaçıncı byte’ında olduğuna göre çalışan bir sorgumuz olması gerekirdi. Bir uart rx kesmesinin içine karmaşık sorgular yazmak akıllıca bir iş değildir, bana güvenin.
Yine modbus spesifikasyonu, iki paket arasında en az 3,5 karakter süresi kadar bir boşluk olması gerektiğini söyler. Bundan da bize iki hisse çıkıyor:
- Bir paket aldığımızda, cevap vermeden önce, master’ın son byte’ı yollamasından beri 3,5 karakter süresi geçtiğinden emin olmamız gerek.
- Bir modbus hattına ilk bağlandığımızda (fiziksel bağlanmayı kastetmiyorum) konuşmaları dinlemeye başlamadan önce en az 3,5 karakter süresi kadar bir sessizlik olmasını beklememiz gerek (paketlerle “senkronize olmak”).

Aslında, üzerinde biraz düşünürseniz yukarıdaki iki maddenin birbirini gerektirmediklerini anlarsınız. Bir slave, master’ın sorgusuna 3,5 karakter süresini beklemeksizin yanıt verirse diğer slave’lerin bakış açısından tek bir paket gelmeye devam ediyor gibi olacaktır ve slave’ler sıralarını kollamaya başlamayacaklardır. Bu bir sorun yaratmaz çünkü o esnada konuşan slave zaten master’a cevap veriyordur.
Haberleşme protokolü dediğiniz şeyler bu şekilde paralel dallanan önermelerle doludur. Protokol yürütme denen şeyde aranan sonuç tamamen mantıksal tutarlılık ya da en sadelik değil, bir şeyi açık şekilde kabul etmek esasına dayanır. O yüzden, bu aşamada fazlasına kafa yormayıp TÜM paketlerin en az 3,5 byte süresi aralıkla hatta yüklenmesini gözeteceğim.
Yukarıda, sözünü ettiğim 3 zaman tanımının haberleşme çerçevesindeki yerlerini gösterdim. Slave taraf işlemcimiz DataProcessor( ) fonksiyonunu çalıştırırken bu süreleri gözetmek zorundadır.
Modbus protokolü süreleri karakter müddeti cinsinden tanımlamış. Yani, bu byte süresi dediğim ölçü birimi bir byte’taki bit sayısı bölü geçerli baudrate kadar olacak demektir. Ancak yine modbus protokolü, yüksek baud rate’ler için sürelerin çok küçük olmasının önüne geçmek için 19200bps üstü hızlar için bu sürenin sabit olabileceğini söylüyor.
Ben en başta bu süreleri tamamen baudrate’e bağlı yapmıştım. Ancak sonra, protokol dediğin şeyde önemli olanın mantıksal bütünlük değil, yaptığını açık şekilde ifade etmek esasına dayandığını hatırlayıp (yukarıda bunu anlattım) tüm zamanlamaları 9600bps’ye göre hesapladım ve sabit yaptım.
Bir byte’ı 10 bit kabul ederek zamanlama parametrelerini şu şekilde alıyorum:
t35 = 4,17ms
t20 = 2,6ms
t15 = 1,56ms

UART RX Kesmesi:
Haberleşme protokolü üç yazılım modülünden oluşur:
Veri alma kesmesi
Veri işlem
Veri yollama
Yukarıda çizdiğim durum makinesinin ilerlemesi iki dış etki ile olur: Sisteme veri gelmesi ya da bir zaman aşımı olması. Bunun haricindeki zamanlarda Data Processor dediğim durum makinesi ya hiçbir şey yapmaz ya da yeni girdiği durum için işletmesi gereken kodu çalıştırıp durağan bir adıma gider. Thread şeklinde algoritma gerçeklemenin benim uyguladığım yöntemi bu.
Bu uygulamada UART kesmesi dediğimizde yalnızca RX kesmesini düşünmemiz yeterli. TX kesme ile yönetilmek zorunda değil. Öyle olması gereken durumlar elbette vardır ama bunu ayrıca anlatırım. RX kesmesi öyle bir şey olmalı ki, içindeki kod DataProcessor( ) ‘ün o anki durumuna bağlı olmamalı. Böylece kesme kodumuzu (işin donanımın parmak soktuğu kısmı) olabildiğince yalın, asıl fonksiyonlarımızla az teması olan bir hale getirmemiz mümkün olur. Bu, embedded programlamada başarıyı çok etkileyen bir şeydir.

Hep bahsettiğim gibi, bir kesme fonksiyonu olabildiğince basit olmalıdır, ama daha basit değil.. 🙂
Byte hatası yoksa gelen byte Xbuffer
‘ın Xptr
indeksli elemanına yazılır ve Xptr
ilerler.
Son olarak da zaman aşımı denetimi için kullandığım timer’ı sıfırlıyorum. Byte geldiği müddetçe zaman aşımı olmamalıdır.
Kesme kodu bundan ibaret olacak. Elbette UART modülünün ürettiği donanım hatalarını da handle edip bir flag ile ana programa bildiriyorum. Ama bu işin ilginç kısımlarından biri değil.
Data Processor Durumları:
DataProcessor( )
bir thread fonksiyonu olarak kullanılır. Yani, ana sistem döngüsünde her geçişte koşulsuz olarak çağrılan bir fonksiyondur ve koşulsuz olarak da sistem kaynağı kullanır ( bir donanım timer’ı ve uart kesmesi). DataProcessor’ün timer overflow ya da uart rx kesmelerine vereceği tepkinin hızı ana sistem çevriminin periyoduna bağlı olacaktır.
DataProcessor( )
her çağrıldığında belli bir durumda olur. Durum geçişleri yalnızca fonksiyonun kendisi tarafından yapılır, yani hiçbir dış kod DataProcessor( )
state machine’in durumunu belirleyen commstat
değişkenine erişemez.
DISABLED:
UART kapalı ve makine devre dışında. Yani modbus fonksiyonumuz kapalı.
Bu durumdan çıkış için kullanıcı kodun comm_cmd = CC_INIT
yapması gerek.
Bu komut algılanınca Restart_RS485( )
fonksiyonu ile UART RX etkinleştirilir.
LISTENING:
UART veri alma etkin, yani makine Modbus hattını dinliyor ancak alınan byte’larla bir işlem yapılmıyor.
Bu durumdan çıkış için T35 süresi boyunca hiç RX olmaması gerekir. T35 süresi boyunca veri alınmaması Modbus hattının boşta durumda olduğu anlamına gelir. Makine artık paketleri dinleyip adresleri kontrol edeceği senrkonize duruma geçebilir.
Eğer bir byte hatası olursa makine SLEEP durumuna atlar.
SYNCD:
3,5 byte süresi boyunca hat sessiz kaldı. Demektir ki şu anda Modbus hattında iletilen bir paket yok. Artık makine senkronize durumda.
Bundan sonra gelen her byte’a bir paketin ilk byte’ı bu deyip adres byte’ı muamelesi yapacağız. Burada iki olasılık var: Paket bizi adresliyordur (ADDRESS_HIT) ya da adreslemiyordur (ADDRESS_MISS).
ADDRESS_MISS için SLEEP durumuna geçilir.
ADDRESS_HIT için de paketin devamını almak için PACKET_RX’e geçilir.
Eğer bir byte hatası olursa makine SLEEP durumuna atlar.
PACKET_RX:
Veri alınıyor.
Bu durumda iken makine Xptr
indeksini izler. Modbus fonksiyonu için ayrılmış buffer boyunun aşılması BUFFER OVERRUN hatası demek olur ve bu durumda paket alımı iptal edilip SLEEP adımına atlanır.
Bu durumdan çıkış paket sonu ile olacak. Paket sonunun algılanması T15 süresi boyunca bir byte alınmaması ile olur.
Eğer bir byte hatası olursa makine SLEEP durumuna atlar.
PARSE:
Paket sonu algılandığında, veri alma kapatılır.
Xptr
sayacı alınan byte sayısını göstermektedir.
Geçerli bir Modbus master paketinde olması gereken genel koşulların sağlaması yapılır. Paket geçerli gözüküyor ise paket türüne bakılır ve ilgili komut işlemine atlanır
Paket hatalı gözüküyorsa SLEEP durumuna atlanır.
SLEEP:
Bir hata ya da ADDRESS_MISS durumunda ortalama bir paket boyu kadar bir süre boyunca makine kapalı durumda bekler. Bu süre baudrate’e göre belirlenir.
Bu sürenin geçmesi sonrasında makine
Restart_RS485( )
seri haberleşme yeniden başlar. Makine LISTENING durumuna gider.
Bundan sonraki makine durumları alınan Modbus komutu ile ilgili işlemler ve verilecek yanıtlar ile ilgili kodlardır. Bunları ayrı ayrı anlatmak yerine desteklediğim Modbus komutlarından söz etmeyi uygun buluyorum:
DESTEKLENEN MODBUS KOMUTLARI

Bu fonksiyon kodlarının kendi başlarına ne anlama geldiklerinin çok fazla konuşulmaya değer bir yanı yok. Önemli olan, üzerinde çalıştığımız cihazda bu kodlar ile neleri okuyup yazacağımızdır. İşin özeti şu: Elinizde bir cihaz vardır. Bu cihazın modbus üzerinden görünür bir bellek haritasını oluşturmuşsunuzdur. Yukarıdaki komutlar artık bu harita üzerinde bir anlam taşır. Bu konuda da olabildiğince özgürsünüz. Holding Register denen şeyle dijital giriş durumu da yollayabilirsiniz. Sonuçta bunu güzelce açıklamayı becerirseniz söz gelimi PLC ile cihazınıza okuma yapan adam holding register olarak okuduğu Word’leri bit olarak kullanabilir. Yani, Modbus’ın tanımındaki veri tiplerinin aslında pek bir bağlayıcılığı yoktur.
Şimdi benden istenen Modbus I/O modüllerinin bellek haritalarına bakalım. Okuyucuya genel bir bakış açısı kazandırmak için şöyle bir not ekleyeyim: Benim için modbus haberleşmeli bir uzak I/O modülü geliştirmek demek 3/4 oranında aşağıdaki tabloları hazırlamak demekti. Bundan sonrası zaten yokuş aşağıdır.
Komutlardan da anlayacağınız gibi Modbus, 4 bellek tipi üzerinde veri erişimi komutlarına sahiptir:
DISCRETE OUTPUTS (Y)
00001 adresinden başlayan dijital çıkış bitleridir.
Bu alan, 0x01 komutu ile okunur ve 0x05, 0x0F komutları ile de buraya yazılır.
Komut adresleri haberleşmede 0 -> 00001 olacak şekilde kullanılır.
DISCRETE INPUTS (X)
10001 adresinden başlayan dijital çıkış bitleridir.
Bu alan 0x02 komutu ile okunur.
Komut adresleri haberleşmede 0 -> 10001 olacak şekilde kullanılır.
ANALOG INPUTS (R)
30001 adresinden başlayan 16 bitlik sadece okunur register alanıdır.
Bu alan 0x04 komutu ile okunur.
HOLDING OUTPUTS (H)
40001 adresinden başlayan 16 bitlik okunabilen ve yazılabilen register alanıdır.
0x03 komutu ile okunur, 0x06 komutu ile yazılır.
X/Y Giriş Çıkışları:
Yaptığım cihazlarda değişik sayıda dijital girişler ve röle çıkışları var. Yani, bu ürünlerin işlevselliği büyük oranda dijital girişleri okumak ve röle çıkışlarını kumanda etmekten ibaret. O yüzden cihazların belirleyici özelliği aşağıda verdiğim X ve Y adres haritalarıdır.
Cihazların gerçek giriş çıkışlarını X ve Y haritalarında 1 adresinden başlayarak konumlandırdım:

Görseli tam boyutlu görmek için TIKLAYIN.

Görseli tam boyutlu görmek için TIKLAYIN
Input Register’ları:
Bu cihazlarda tam sayı olarak ifade edilebilecek bir giriş ya da çıkış bulunmuyor. Ancak, dijital giriş çıkışları birer bitmap olarak bu bellek haritasında okunabilir yapmak iyi bir fikir. Bu şekilde girişleri okumak için ayrı çıkışları okumak için ayrı bir komut kullanmak zorunda kalınmadan tek seferde her şey okunabilir.

Görselin tam boyutunu görmek için TIKLAYIN
Holding Register’lar:
Ben bu bellek alanını, yazılabilir olduğu için input register alanından ayrı tanımlıyorum. Buradaki “holding” sıfatına saygı olarak, cihazların silinmez belleğine yazılan parametreleri bu haritada tanımladım. Yine de bu haritanın bir kısmını son kullanıcı için sadece okunur yaptım (kullanıcının cihaz kimlik bilgilerini değiştirebilmesini istemem).
Input register alanında X ve Y vektörlerini bitmap olarak göstermeye benzer şekilde burada da Y vektörünü binary olarak yazma özelliği düşünülebilirdi ama zaten 0x0F komutu tam olarak bu işi yaptığı için böyle bir şeye gerek yok.

Tablonun tam açıklamalarını görmek için TIKLAYIN
MODBUS Komutları:
MODBUS komutlarının benim uygulamalarımdaki yürütmesi belli kısıtlamalara tabi olabilir. Sonuçta protokolün kendisi çok genel olduğu için biz bazı yönleri bizdeki özelliklere ve kısıtlamalara göre basitleştirebiliriz.
Örneğin, discrete okuma komutlarında hedef adresinin 8’in katları şeklinde olmasını şart koşuyoruz. Benzer şekilde, 8’in katı olmayan vektör boylarını da kabul etmiyoruz. Bu işlevsel bir kısıtlama değil. Çünkü bu şart, genel modbus tanımlamasına bir istisna oluşturmuyor. Yalnızca onun bir alt kümesini geçerli kabul ediyor.
Ayrıca, 0x04: Read Input Register komutunda komutun serbestçe hazırlanmasına izin vermiyoruz. Bu komutu kullanacak kişi bir seferde cihazın 4 word’lük R bellek alanının tamamını okumak zorunda.








Son olarak, yukarıda anlattıklarımın kodda nasıl gözüktüğünü paylaşmak istiyorum.
Ana programda koşulsuz olarak çağrılan bir thread fonksiyonum var:
RS485Thread[rs485_status] ();
Bu çağrı aslında bir fonksiyon pointer çağrısı. Dizinin elemanları şunlar:
static void (* RS485Thread[]) () =
{
RS485_Disabled,
RS485_Listening,
RS485_Syncd,
RS485_Receiving,
RS485_Parse,
RS485_Handler_01,
RS485_Handler_02,
RS485_Handler_04,
RS485_Handler_05,
RS485_Handler_0F,
RS485_Response
};
Daha önce anlattığım dinleme, senkronize olma, veri alma ve alınan veriyi çözümleme işlemlerinin her biri bir fonksiyon adımı olarak işletiliyor. RS485 işlerini yürüttüğüm ana thread bu adımlar arasında dönüp duruyor.
/// RS485 durumları:
typedef enum
{
DISABLED = 0, // Transceiver devre dışı
LISTENING, // Senkronize olma süresi bekleme
SYNCD, // rs485 hattına senkronize olunup paket başı bekliyor olma
RECEIVING, // Cihaz adreslendi, veri alınıyor
PARSE, // veri paketi tamamlandı, içeriğe bakılıyor
HANDLER_01, // READ DISCRETE OUTPUTS komut handler'ı
HANDLER_02, // READ DISCRETE INPUTS komut handler'ı
HANDLER_04, // READ INPUT REGS komut handler'ı
HANDLER_05, // WRITE DISCRETE OUTPUT komut handler'ı
HANDLER_0F, // WRITE MULT.DISC. OUTPUT komut handler'ı
RESPONSE // yanıt paketini yollama adımı:
} RS485_STATUS;
static RS485_STATUS rs485_status; // RS485 thread control
unsigned char uart_tick; // uart zamanlama sayacı
uart_tick, sistem tarafından işletilen birkaç ms periyotlu bir serbest timer. Thread’in zamanlaması için buna ihtiyacımız var. Ayrıca, bize veri geldiğinde tetiklenen bir de kesme lazım:
void interrupt HI_ISR(void)
{
// UART RX kesmesi :
if (PIR1bits.RC1IF == 1)
{
Xbuffer[Xptr] = RCREG1;
++Xptr;
uart_tick = 0;
PIR1bits.RC1IF = 0;
}
}
İronik bir şekilde bu yazıyı artık pek kullanmadığım PIC18 işlemcisi üstünde çalışan bir kod ile örneklendirdim. Bu işlemcide artık bana bile tuhaf gelen bir şekilde, kesmeler için müşterek bir vektör var. O yüzden kesme handler’ı içinde bir kez daha receive interrupt flag ‘i sorguluyoruz. Ayrıca burada uart hatası kesmesi diye bir şey yok ki bunu handle etmemek bana pek doğru gelmiyor. Neyse, bunların modbus’ın kendisi açısından pek bir önemi yok. Biz şimdi sözü state fonksiyonlarına bırakıp bu yazıyı noktalayalım:
////////////////////////////////////////////////////////////////////////////////
//// RS485 - Modbus State Functions:
////////////////////////////////////////////////////////////////////////////////
// seri port kapalı: bir timer olayı ile yeniden başlatılacaktır.
static void RS485_Disabled(void)
{
// Bu durumda iken, tick gelmesini bekleriz:
if ( uart_tick > 1) // uart timer'ından kesme gelene kadar burada dur...
{
uart_tick = 0;
// UART'ı yine aç:
Xptr = 0;
RS485_DIR = 0; // alici modu
RCSTA1bits.SPEN = 1;
RCSTA1bits.CREN = 1;
PIE1bits.RC1IE = 1;
rs485_status = LISTENING; // veri bekleme modunda bekleyeceğiz
}
}
// alıcı boşta: (yani master'dan komut bekleme adımı)
static void RS485_Listening(void)
{
/// sync beklerken buffer overrrun olabilir:
if ( Xptr > 19 ) Xptr = 0;
/// senkronize duruma gelmek için T35 timeout'u bekiyoruz:
if ( uart_tick > 1 )
{
uart_tick = 0;
Xptr = 0;
rs485_status = SYNCD;
}
}
// alıcı dinleme moduna geçti: gelen byte paket başı olacak.
static void RS485_Syncd(void)
{
if ( Xptr ) // başlık alındı mı?
{
// addr. hit =>
if (( Xbuffer[0] == my_rs485_address)||( Xbuffer[0] == 255 ))
{
LED2 = 1;
uart_tick = 0;
rs485_status = RECEIVING;
}
else // addr. miss =>
{
Disable_RX();
rs485_status = DISABLED;
}
}
}
// veri alınıyor: bizi adresleyen bir paket geliyor.
static void RS485_Receiving(void)
{
// üzerinde çalışılabilecek max. rx paket boyu aşıldıysa yanıt verilmeyecek:
if ( Xptr > 29 )
{
Disable_RX();
rs485_status = DISABLED;
}
else if ( uart_tick > 1 )
{
Disable_RX();
rs485_status = PARSE;
}
}
static void RS485_Parse(void)
{
unsigned char c;
////////////////////////////////////////////////////////////////////////////
// gelen paketin doğruluğunun kontrolü
// komut byte'ı doğru mu:
if ( ( Xbuffer[1] > 0x10 )||( Xbuffer[1] == 0 ) )
{
rs485_status = DISABLED;
}
// paket boyu beklenen en düşük değerin de altındaysa:
else if ( Xptr < 8 )
{
rs485_status = DISABLED;
}
// bu aşamada gelen verinin CRC'sini sorguluyoruz:
else
{
myCRC.i = 0xFFFF;
c = Xptr-2; // crc hesabına girecek son byte indeksi
// gelen paketin crc'sini hesapla: (0..n-2 byte'lar arası)
for (Xptr=0; Xptr<c; Xptr++)
{
DoCRC(Xbuffer[Xptr]);
}
// TODO: crc doğru mu bakmak!
// gelen crc hesapladığımızla aynı mı:
/*
if ( ( myCRC.b[0] == Xbuffer[c] ) && ( myCRC.b[1] == Xbuffer[c+1] ) )
{
if ( Xbuffer[1] == 0x01 ) rs485_status = HANDLER_01;
}
else rs485_status = DISABLED;
*/
if ( Xbuffer[1] == 0x01 ) rs485_status = HANDLER_01;
else if ( Xbuffer[1] == 0x02 ) rs485_status = HANDLER_02;
else if ( Xbuffer[1] == 0x04 ) rs485_status = HANDLER_04;
else if ( Xbuffer[1] == 0x05 ) rs485_status = HANDLER_05;
else if ( Xbuffer[1] == 0x0F ) rs485_status = HANDLER_0F;
else rs485_status = DISABLED;
}
}
/// Read Discrete Outputs
static void RS485_Handler_01(void)
{
// genel çağrı adresi için bu komut yanıtlanmaz:
if ( Xbuffer[0] == 255 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
// vector start 0 olmalı:
else if ( Xbuffer[2] & Xbuffer[3] )
{
uart_tick = 1;
rs485_status = DISABLED;
}
// vector size = 32 olmalı
else if ( Xbuffer[4] != 32 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
// her şey doğru ise:
else
{
// çıkışların geçerli durumunu oku:
Read_Y();
// yanıt paketini hazırla:
Xbuffer[0] = my_rs485_address;
Xbuffer[1] = 1;
Xbuffer[2] = 4;
Xbuffer[3] = dY[0].b[0];
Xbuffer[4] = dY[0].b[1];
Xbuffer[5] = dY[1].b[0];
Xbuffer[6] = dY[1].b[1];
// yanıt paket boyu:
response_size = 7;
rs485_status = RESPONSE;
}
}
/// Read Discrete Outputs
static void RS485_Handler_02(void)
{
// genel çağrı adresi için bu komut yanıtlanmaz:
if ( Xbuffer[0] == 255 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
// vector start 0 olmalı:
else if ( Xbuffer[2] & Xbuffer[3] )
{
uart_tick = 1;
rs485_status = DISABLED;
}
// vector size = 48 olmalı
else if ( Xbuffer[4] != 48 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
// her şey doğru ise:
else
{
// girişlerin geçerli durumunu oku:
Read_X();
// yanıt paketini hazırla:
Xbuffer[0] = my_rs485_address;
Xbuffer[1] = 2;
Xbuffer[2] = 6;
Xbuffer[3] = dX[0].b[0];
Xbuffer[4] = dX[0].b[1];
Xbuffer[5] = dX[1].b[0];
Xbuffer[6] = dX[1].b[1];
Xbuffer[7] = dX[2].b[0];
Xbuffer[8] = dX[2].b[1];
// yanıt paket boyu:
response_size = 9;
rs485_status = RESPONSE;
}
}
static void RS485_Handler_04(void)
{
unsigned char a, b, i;
// genel çağrı adresi için bu komut yanıtlanmaz:
if ( Xbuffer[0] == 255 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
else if ( (Xbuffer[2] > 11) || (Xbuffer[4] > 12) )
{
uart_tick = 1;
rs485_status = DISABLED;
}
else if ( (Xbuffer[2] + Xbuffer[4]) > 12 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
else
{
// dR alanında haritalanmış dX ve dY vektörlerini güncelle:
Read_X();
Read_Y();
dR[2].i = dX[0].i;
dR[3].i = dX[1].i;
dR[4].i = dY[0].i;
// yanıt paketini hazırla:
a = Xbuffer[2]; // okuma vektörü başlangıç adresi
b = Xbuffer[4]; // okuma vektörü eleman boyu
// header:
Xbuffer[0] = my_rs485_address;
Xbuffer[1] = 4;
Xbuffer[2] = 2*b;
// dR okuma vektörünü kopyala:
for ( i=0; i<(a+b); i++ )
{
Xbuffer[2*i+3] = dR[a+i].b[0];
Xbuffer[2*i+4] = dR[a+i].b[1];
}
response_size = 2*b + 3;
rs485_status = RESPONSE;
}
}
static void RS485_Handler_05(void)
{
unsigned char bix;
unsigned int mask;
// operator word'ünün Hi byte'ı kesinlikle 0 olmalıdır:
if ( Xbuffer[4] )
{
uart_tick = 1;
rs485_status = DISABLED;
}
else if ( Xbuffer[2] > 31 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
else
{
// adreslenen bit dY[1] üzerinde ise:
if ( Xbuffer[2] > 15 )
{
bix = Xbuffer[2] - 16;
mask = (unsigned int) ( 1 << bix );
if (Xbuffer[5] == 0) dY[1].i &= ~(mask);
if (Xbuffer[5] == 0xFF) dY[1].i |= mask;
if (Xbuffer[5] == 0x11) dY[1].i ^= mask;
}
// adreslenen bit dY[0] üzerinde ise:
else
{
bix = Xbuffer[2];
mask = (unsigned int) ( 1 << bix );
if (Xbuffer[5] == 0) dY[0].i &= ~(mask);
if (Xbuffer[5] == 0xFF) dY[0].i |= mask;
if (Xbuffer[5] == 0x11) dY[0].i ^= mask;
}
UpdateOutputs();
// komuta verilecek yanıt, komutun aynısının tekrar edilmesidir:
response_size = 6;
rs485_status = RESPONSE;
}
}
/*
*
*
*/
static void RS485_Handler_0F(void)
{
// bit address = 0, bit count = 32 olmalı.. H byte'lar önemli değil..
if ((Xbuffer[2]) || (Xbuffer[4] != 32))
{
uart_tick = 1;
rs485_status = DISABLED;
}
else if ( Xbuffer[6] != 4 )
{
uart_tick = 1;
rs485_status = DISABLED;
}
else
{
// gelen güncellemeyi dY vektörüne al:
dY[0].b[0] = Xbuffer[7];
dY[0].b[1] = Xbuffer[8];
dY[1].b[0] = Xbuffer[9];
dY[1].b[1] = Xbuffer[10];
// ve output vektör güncellemesini çıkışlara yansıt:
UpdateOutputs();
// yanıt paketi, gelen paketin ilk 6 byte'ı + CRC:
response_size = 6;
rs485_status = RESPONSE;
}
}
/*
* Bu fonksiyon Xbuffer[ ] dizisindeki response_size adet payload'u seri porttan yollar
* Ardına da yollama esnasında hesaplanan 2 byte CRC'yi ekler.
*/
static void RS485_Response(void)
{
unsigned char i;
// not: Response fonksiyonuna girilmeden önce uart kesmeleri kapatılmış olmalı!
RS485_DIR = 1; // transceiver'ı gönderme moduna sok..
RCSTA1bits.CREN = 0; // uart alıcısını kapat..
TXSTA1bits.TXEN = 1; // uart göndermesini devreye al..
myCRC.i = 0xFFFF; // CRC hesaplama değişkenini başlat
// gönderilen her byte için crc hesabı yapılmalı
for (i=0; i<response_size; i++)
{
TXREG1 = Xbuffer[i];
DoCRC(Xbuffer[i]);
while (TXSTA1bits.TRMT == 0) ;
}
// payload gittikten sonra CRC'yi yolla:
TXREG1 = myCRC.b[0];
while ( TXSTA1bits.TRMT == 0 );
TXREG1 = myCRC.b[1];
while ( TXSTA1bits.TRMT == 0 );
TXSTA1bits.TXEN = 0;
uart_tick = 1;
rs485_status = DISABLED;
RS485_DIR = 0;
}