Modbus Function Codes
Bu yazıda, Modbus standartında belirtilen Function Code lara bakacağız. Serideki diğer yazılarda Modbus ve Modbus RTU’yu konuştuk:
1-127
arası function code’ların geçerli olduğundan bahsetmiştik. Bir
hatırlayalım:
Bu aralıktaki kodların bir kısmı Modbus tarafından PUBLIC olarak işaretlenmiş ve genel amaçlıdır. Bir kısmı ise User Defined olarak bırakılmıştır.
Her bir function code’un anlamı ve ne yapılacağı Modbus dokümanında anlatılmıştır. [1] Bunları tekrar yazmak pek anlamlı değildir. Temel kavramları aktarmaya çalışacağım. RTU ile ilgili bilgileri de Modbus dokümanından alacağım. [2]
Read Coils, 0x01
Modbus protokolünde coil 1-bit genişliğinde yazma ve okuma yapılabilen bir alan olarak düşünülebilir. Bu komut server’dan (RTU’da slave) bu register’ları okumak için kullanılır. Modbus’a göre 65536 adet coil olabilmektedir. Bu komut ile okunmak istenen coillerin base adresi ve range’i verilir. Ardışıl olan coiller okunabilir. Bir komut ile maksimum 2000 adet coil okunabilir. Cevap paketinde de her coil 1 bit ile ifade edilir.
İstek:
Function code:
0x01
Data, ilk 2 byte:
0x0000-0xFFFFF
, coil başlangıç adresi yani okunacak en düşük adresli coil adresi.Data, sonraki 2 byte:
1-2000
, kaç adet coil’in okunacağı.
İstek paketi toplam 5 byte’tan oluşmaktadır. Modbus bellek modelinde
paketlerdeki adresleme ile bellek adresleri arasında 1 fark olduğundan
bahsetmiştik. Yani cihaz belleğinde coil adresleri 1-65536
arasında iken
haberleşme sırasında coiller 0-65535
ile adreslenir. Bu tüm veri modelleri
için geçerlidir.
Cevap:
Function code:
0x01
Data, ilk 1 byte: Arkada kaç byte’lık data olacağı,
N
diyelimData, sonraki
N
byte: Coil status bilgileri
Eğer 16 adet coil status okunmak istendiyse N
2 olacaktır fakat 17 adet
istendiyse 3 olacaktır. Sayının 8’e bölümünün üste yuvarlanması ile N
sayısı
elde edilir.
Diyelim ki 20-39
adreslerindeki coilleri okumak istiyoruz. Modbus RTU üzerinde
aşağıdaki gibi olacaktır. Modbus’ın byte order’nın big endian, RTU’daki CRC’nin
ise little endian olduğunu hatırlayalım.
İstek:
| Adres | 0x01 | 0x00 | 0x13 | 0x00 | 0x14| CRC-low | CRC-high |
Başlangıç adresimiz 20 fakat haberleşmede 1 eksiğini alıyoruz, 19 yani 0x13
oluyor. Okumak istediğimiz aralık ise 20 = 39 - 20 + 1
adet coil içeriyor,
yani 0x14.
20/8 = 2.5, bunu 3’e yuvarlıyoruz yani 3 byte cevap vereceğiz.
Cevap:
| Adres | 0x01 | 0x03 | Coil 27-20 | Coil 35-28 | Coil 39-36 | CRC-l | CRC-h |
olacaktır.
Coil status kısmı, base adres ile başlar yani en düşük adresli coil. İlk byte’ın
LSB biti en düşük adresli coil’in durumunu içermelidir. 1
, ON; 0
, OFF
demektir. Örneğin slave bu byte’ın değerini 1010 1101
olarak gönderirse:
Bit: 1 0 1 0 1 1 0 1
Coil: 27 26 25 24 23 22 21 20
olarak anlamdırırız. Fakat unutmayın ki Modbus RTU’da data’nın önce LSB biti
konur. Yani osiloskop ile bakıyorsak hatta aslında 1011 0101
görürüz.
Adresleme bu şekilde artarak devam eder. En son byte’ta bpşluklar olabilir, örneğin bu örnekte 4 bit padding yapılması gerekmektedir. Bunlar 0 ile pad edilmelidir. Son byte:
Bit: 0 0 0 0 1 1 1 1
Coil: -- -- -- -- 39 38 37 36
gibi…
Bir de hata durumlarına bakalım. Dokümanda gayet güzel gösterilmiş:
Görebileceğimiz gibi farklı hata durumlarında farklı hatalar dönmelidir. Hata
durumlarında 1 byte function code, istek function code’un 0x80
ile ORlanması
ile oluşturulur. Daha sonra 1 byte data olarak ExceptionCode
konur.
Diyelim ki master olmayan bir adresteki coil’i okumak istedi. Bu durumda da 02
nolu exception dönüyorsak
Cevap:
| Adres | 0x81 | 0x02 | CRC-l | CRC-h |
olacaktır.
Yazının geri kanalındaki komutların çalışma biçimi bu komuta benzediği için daha yüzeysel anlatacağım.
Read Discrete Inputs, 0x02
Read coils, 0x01
, ile aynı çalışmaktadır.
Read Holding Registers, 0x03
Coil ve discrete input okumak ile benzerdir özünde. Holding Registers, Modbus standartında 16-bit olarak tanımlanmıştır. Buna göre protokolde değişiklikler olmaktadır. Farklı olarak tek seferde en fazla 125 adet register okunabilir, her biri 16-bit. Bir register 2 byte şeklinde gönderilir. Modbus’a uygun olacak şekilde ilk olarak MSB byte hatta konur, yani big endian order kullanılır.
Read Input Registers, 0x04
Read Holding Registers, 0x03
, ile aynı çalışmaktadır.
Write Single Coil, 0x05
Tek 1-bit lik coil register’ın değerini değiştirmek için kullanılır.
RTU İstek:
| Adres | 0x05 | 0x0000 - 0xFFFF | 0x0000 or 0xFF00 | CRC-l | CRC-h |
CRC ve adres hariç 5 byte’lık bir pakettir. Function code sonrası ilk 2 byte
yazma yapılacak regsiter adresini belirtir. Önceki paketlerde olduğu gibi 1
offset olayı burada da vardır. İlgili register’ı ON
yapmak için 0xFF00
,
OFF
yapmak için 0x0000
yazılır. Diğer değerler geçersizdir.
Write Single Register, 0x06
Tek bir holding register’a (16-bit) yazmak için kullanılır. Write Single Coil,
0x05
, e oldukça benzer. Data kısmı sabit iki adet 16-bit veri yerine yazılması
istenen 16-bit veridir.
Read Exception Status, 0x07
Sadece seri kanal implementasyonlarında vardır. 8-bit bir değer okunuyor ama ne işe yarıyor anlamadım.
Diagnostics, 0x08
Sadece seri kanal implementasyonlarında vardır. Slave cihazdan (server) çeşitli sorgular ve test yapmaya yarar. 2 byte’lık sub-function code’lar ile istek şekillendirilir. Detayları Modbus dokümanında vardır.
Loopback test
Bir slave cihazı susturmak
İletişimi resetlemek
Çeşitli istatistik register’larını okumak için
kullanılır.
Get Comm Event Counter, 0x0B
ve Get Comm Event Log, 0x0C
Sadece seri kanal implementasyonlarında vardır. Detayları dokümanlardan okunabilir. Özünde seri kanal ile çeşitli istatistikleri döner.
Write Multiple Coils, 0x0F
Birden fazla 1-bit genişliğinde olan coil’e yazmaya yarar. Write Single Coil,
0x05
den farkı birden fazla, ardışıl 1-bit register’a yazma imkanı sunmasıdır.
Tek seferde en fazla 1968
adet register’a yazma yapılabilir.
Write Multiple Registers, 0x10
Ardışıl 16-bit genişliğindeki holding register’lara yazma yapmayı sağlar.
Write Single Register, 0x06
komutundan farklı olarak birden fazla register’a
tek seferde yazmaya yarar. Bu komut ile tek seferde 123
adet register’a kadar
yazma yapılabilir.
Report Server ID, 0x11
Sadece seri kanal implementasyonlarında vardır. Cihaza özgü bir cevabı vardır.
Read File Record, 0x14
Modbus’ta file denen bir kavram vardır. Fiziksel olarak tam neye karşılık geldiği bence net değil, üreticiye bırakılmış. Örneğin data logger gibi bir cihazda file, capture edilen bir waveform’u gösterebilir. [3] Ya da uzaktan yazılımını güncelleyeceğimiz bir cihazın flash’ında duran yazılımı bir file gibi kurgulayabiliriz. Bu kısım, slave cihaz üreticisine bağlı.
Kurgusual olarak her bir dosyanın bir numarası vardır, bu numara aralığı
1-65535
arasındadır. Bu numara, dosya ismi gibidir. Her bir dosya record
adı verilen 2-byte genişliğindeki parçalara bölünmüştür. Her bir dosya da
en fazla (?), 10000
adet record bulunabilir. Böylece bir dosya en fazla
20000 = 2 * 10000 byte, ~19.53 KB
boyutunda olabilir.
Bu komut ile istenirse birden fazla file’dan ardışıl olmak üzere birden fazla uzunlukta record okunabilir. Detayları dokümanında vardır. Protokol açısında maksimum paket boyutunu geçmememiz lazım.
Write File Record, 0x15
Okuma komutuna benzer, paket formatı olarak da. Bu da yazma yapmak için kullanılır.
Mask Write Register, 0x16
Bir adet 16-bit genişliğindeki holding register’a doğrudan veri yazmadan, bit bit bazı bitleri AND ve OR işlemine tutarak, yani maskeleyerek, set veya reset etmeye yarar. Yani diyelim ki 10.bit’i set etmek, aynı anda 4.bit’i reset etmek için bunu kullanabiliriz.
Read/Write Multiple registers, 0x17
Tek bir transaction ile birden fazla 16-bit holding register’ı, ardışıl olmak şartı ile, okuma yapmaya ve yazma yapmaya (aynı adres aralığında olmak zorunda değil okuma ile yazma fakat ikisi de kendi içinde ardışıl olmalı) yarar. Modbus standardına göre önce yazma sonra okuma yapılır.
Read FIFO Queue, 0x18
Anladığım kadarıyla Modbus’ta genişliği 16-bit yani bir holding register
genişliğinde olan bir FIFO data modelinden bahsediliyor. Bu FIFO teorik
olarak 65535 + 31
derinliğinde. Bu komut ile FIFO’nu herhangi bir offsetine
gidip maksimum 31
adet veri okuyabiliyoruz. Bu komut ile FIFO’dan okuma
yapıldığı zaman FIFO içeriği silinmemektedir.
Encapsulated Interface Transport, 0x2B
Bu komut ile Modbus’a tünelleme yaptırılabilir. SSH Tünel gibi Modbus üzerinden başka bir protokolün taşınması sağlanabilir. Modbus dokümanlarında CANOpen mesajlarının taşınması anlatılmıştır. Tünelleme ihtiyacı olunca dönüp bakılabilir.
MODBUS Exception Responses
Modbus dokümanlarında çeşitli exception’lar tanımlanmıştır. Bunlar
protokollerin içinde de anlatılmaktadır. Diyelim ki desteklenmeyen komut attık,
yanlış parametre attık, bu durumda exception response gelecektir. Detayları
dokümanda anlatılmıştır. Bu durumda daha önceden bahsettiğimiz gibi
function code, 0x80
ile ORlanmaktadır.