Modbus register'larına temiz bir şekilde nasıl erişebilirim?

Başlatan Tagli, 23 Kasım 2018, 18:35:16

Tagli

Aslında sorunum doğrudan Modbus ile ilgili değil ama benim karşıma Modbus ile uğraşırken çıktı. Sorun şu:

Elimizde Modbus holding register olarak kullandığımız 3 tane uint16_t değişken olsun: a, b ve c. Bunları herkesin bu isimlerle ulaştığı global değişkenler olarak düşünelim. Kodumuzun Modbus iletişiminden sorumlu kısmı ise bunlara isimleri ile değil, adresleri ile ulaşmak istiyor, çünkü kendisine gelen komutlar bu şekilde olacak. Yani dışarıdan birisi a'ya erişmek istediğinde, ilgili kod holdingRegisters[0] üzerinden a'ya ulaşacak. Ama kodun kalan kısmı bunu doğrudan a şeklinde kullanacak. Derdim bunu temiz bir şekilde koda dökebilmek.

Akla gelebilecek kimisi saçma birkaç çözümü sıralayayım:

1) Kodun içinde de adres ile anmak: Bu en kötü seçenek. Kodun içinde a ifadesini görürsem ne olduğunu bilirim ama a yazılması gereken her yere holdingRegisters[0] yazamam. Çok pis bir kod olur.

2) a'yı pointer olarak tanımlamak: Bu durumda başta bir adres atama işi gerekecek, aşağıdaki gibi.
uint16_t *a = holdingRegisters + 0;
uint16_t *b = holdingRegisters + 1;
uint16_t *c = holdingRegisters + 2;
Sonra da bu değişkenleri a şeklinde değil, *a şeklinde kullanmak zorunda kalacağız. Adres tanımlamaları örneğini uint16_t üzerinden verdiğim için temiz gibi gözüküyor ama değişkenler mesela int32_t gibi farklı bir tür olsa üstüne bir de pointer casting yapmamız gerekecek. Ama tabi asıl sorun her yerde *a şeklinde yazmak zorunda kalmak bence.

3) C++ kullanmak: Çalıştığımız platform için C++ derleyicisi kullanma imkanımız varsa, yukarıdaki 2 numaralı yöntemin benzerini pointer değil de referans kullanarak yapabiliriz. Bu bizi baştaki tanımlamalardan kurtarmaz, şuna benzer bir şekle sokar:
uint16_t &a = *(holdingRegisters + 0);
uint16_t &b = *(holdingRegisters + 1);
uint16_t &c = *(holdingRegisters + 2);
Ama en azından artık kodun içinde *a yazmaya gerek kalmadan doğrudan a yazıp kullanabiliriz değişkeni. Elbette C++ kullanmak her zaman mümkün olmuyor ve tanımlama kısmı da az da olsa gıcığıma gitmeye devam ediyor.

4) Dizi yerine pointer tanımlayıp değişkenleri struct içine koymak: Önceki yöntemlerden farklı olarak bu sefer işi tersten yapacağız. uint16_t holdingRegisters[3]; şeklinde bir alanımız yok. Sadece uint16_t *holdingRegisters; şeklinde boş bir pointer tanımlayıp, sonra da bir yerlerde holdingRegisters = &a; diyebiliriz. Sadece a için bunu yapmamız da yeterli olur, b ve c için ayrıca tanımlama yapmayız. Sorun şu ki bu durumda a, b ve c'nin hafızada birbiri ardına adreslerde olması gerek. Bunları bir struct içine koyarsak bu şartı sağlayabiliriz, örneğin struct'ın adı hr olsun. Ama bu sefer de program bu değişkenlere hr.a şeklinde erişmek zorunda kalacak. Yine pis bir yazım şekli.

5) Pointer dizisi tanımlamak: 4 numaraya benzer bir çözüm. Ama bu sefer uint16_t *holdingRegisters[3]; gibi bir tanımlama yapmamız ve holdingRegisters[0] = &a; şeklinde tüm değişkenleri atamamız gerekecek. 16 bitten farklı boyutlu değişkenler işi ayrıca karıştıracak. Kodumuzda temiz bir şekilde a'yı kullanabileceğiz, ama fazladan bir pointer dizisini bellekte tutmamız gerekecek. Yine de diğerlerine göre akla daha yatkın sanki...

6) Dizi yerine pointer tanımlayıp... hayvanlık yapmak: Aynı 4 gibi, ama struct yok. Tam bir hayvanlık. İtiraf edeyim ki bunu denedim. Bir .c dosyasına arka arkaya sıraladım değişkenlerimi. Sonra holdingRegisters = &a; dedim. Denemeyi XC16 ile yapmıştım ve derleyicinin ürettiği .map dosyasına bakarak adresleri kontrol ettim. Şanslıydım, a, b ve c gerçekten de arka arkaya adreslere yerleşmişti. Program sorunsuz bir şekilde çalıştı. Bu haliyle tehlikeli ama en temiz çözüm. Sonra STM32 için TrueStudio ile benzer bir deneme yaptım (gcc kullanarak). Bu sefer olmadı. map dosyasına baktım, değişkenlerin sırasını kendi kafasına göre değiştirmiş...

7) Anonim struct tanımlamak: Bunu denemedim, sadece aklıma geldi. a, b, c anonim (isimsiz) struct içinde olursa hr.a yazmama gerek kalmaz ve 6'daki yöntemi güvenle kullanabilirim sanırım. Ama bir dosyada toplanmış isimsiz struct'ları nasıl extern edebilirim? Bu mümkün mü ki?

Sizin tavsiyeniz nedir? En az yazım gerektirecek ve göze hitap edecek yöntem nedir? Veya bunlar dışında yapabileceğim bir şey var mı?
Gökçe Tağlıoğlu

RaMu

Şuna benzer birşey belki olabilir:
#define a (&holdingRegisters + 0);
#define b (&holdingRegisters + 1);
#define c (&holdingRegisters + 2);
Registerlar 16 bit ise 0,2,4 eklemek lazım. ?

6. daki problem için C de ilgili registerların ardışık yerleştirileceğini söyleyen bir sistem olması lazım. Hatta şu ram adresine yerleştir de denebiliyor olması lazım.

Soru aslında kafamda tam canlanmadı henüz.
Sorularınıza hızlı cevap alın: http://www.picproje.org/index.php/topic,57135.0.html

Tagli

#define yöntemi belki işe yarayabilir. Gerçi sanırım başında bir de * olması lazım, *(&holdingRegisters + 0); şeklinde. Ama bu durumda gerçekte a diye bir değişken var olmuyor. 1. maddenin biraz düzeltilmiş şekli gibi. a uint16_t olmazsa sorun çıkacak ve işler karışacak sanırım.

Alıntı yapılan: RaMu - 24 Kasım 2018, 02:22:02Registerlar 16 bit ise 0,2,4 eklemek lazım. ?
holdingRegisters uint16_t* tanımlandığı için +2, +4 demek gerekmiyor. Ama gerektiği zamanlar var tabi. a, b, c 32 bit olduğu zamanlarda öyle yapmak gerekiyor. Bu da hata yapmaya açık bir durum. Hoşuma gitmemesinin bir sebebi de o.

Alıntı yapılan: RaMu - 24 Kasım 2018, 02:22:026. daki problem için C de ilgili registerların ardışık yerleştirileceğini söyleyen bir sistem olması lazım.
struct içinde koyma haricinde bu nasıl yapılabilir bilmiyorum. Eğer bir yöntemi varsa benim sorunumu en temiz çözen şey o olur sanırım.

Alıntı yapılan: RaMu - 24 Kasım 2018, 02:22:02Hatta şu ram adresine yerleştir de denebiliyor olması lazım.
Evet, bu mümkün. Ama elle adres atamak fazla zahmetli çünkü bildiğim kadarıyla her değişken için bunu yapmalıyım. Ayrıca derleyicinin işine o kadar karışmak istemiyorum. Bu durum kodu başka işlemciye taşımak gerektiğinde de sorun çıkaracaktır.
Gökçe Tağlıoğlu

Cemre.

Eğer sıralı bir adres listeniz olacaksa, yani iki adres arasında atlama sıçrama yoksa basit bir array tanımı ile yapılabilir diye düşünüyorum, umarım ihtiyacı doğru anlamışımdır...

unsigned short RegisterMap[100]; şeklinde tanımladığınız bir array'i doğrudan kullanabilirsiniz.

Modbus fonksiyonlarınızı işlerken eğer bir offset değeriniz varsa onu da hesaba katarak örn;
60000 offset ile birlikte 60005'inci register'ı okumak istediğinizde siz gelen 60005 adres no'dan 60000 çıkartarak RegisterMap[5] değişkenini dönebilirsiniz diye düşünüyorum. Ben mi çok basit düşünüyorum ;D

kantirici

Taşınabilir, modifiye ye müsait bir fonksiyon yazmak en ideali bence.

u32 modbus_get_data(u32 addr, u8 *val, u8 type)
{
  u32 res = 0;
  switch(addr)
  {
  case 6005:
   memcpy(val, &a, sizeof(a));
   break;
  }
  return res;
}

Sıralı okumalar içinde fonksiyon aşağıdaki gibi olabilir.

u32 modbus_get_data(u32 addr_f, u32 addr_l, u8 *buff, u8 type)
{
	for(int i = addr_f; i < addr_l; i++)
	{
		  switch(i)
		  {
		  case 6005:
			  memcpy(buff, &a, sizeof(a));
			  buff += sizeof(a);
			  break;
		  }
	}
}

RaMu


CcsC ile denedim, tam Ansi C değil, uygun bir tanımmıdır bilemiyorum.

Aşağıdaki delay süresini bir ledin yanıp sönme süresi olarak kullandım,
array[0] = X ve A_reg = X olarak değeri program içerisinde runtime arttırarak azaltarak
değişik sürelerde ledin yanması olarak proteusta gözlemledim, çalışıyor.

//8bit mcu ile denedim, tanımlarken 0,2,4 eklemek gerekti tabiki

//TANIM
unsigned int16 delay_times[3];
#WORD  A_reg      = delay_times + 0
#WORD  B_reg      = delay_times + 2
#WORD  C_reg      = delay_times + 4

//KULLANIM:
delay_times[0] = 0;
//veya
A_reg = 0;
Sorularınıza hızlı cevap alın: http://www.picproje.org/index.php/topic,57135.0.html

Elektroemre

Bence en güzeli C++ gösterimi ama union içinde untagged struct da diğer bir seçenek olabilir. Derleyicinin untagged struct'ı desteklemesi lazım tabi. Birde aligment dan emin olmak için platforma bağlı olarak, pack(2) yapmaya zorlanması lazım derleyicinin.

quarko

@Tagli hocam, ben modbus register larına erişimini structure tanımlayarak yapıyorum. Holding Register lar için ayrı, Input Register lar için ayrı vs... Örneğin;

stModbusRegister_t HoldingRegisters[HOLDING_REGISTER_NUMBERS]= { { (uint16_t*) &VariableX,  HR_0},
                                                                { (uint16_t*) &VariableY,  HR_1},
                                                                { (uint16_t*) &VariableZ,  HR_2}
};

gibi...

Her bir modbus parametresinin bir gerçek adresi bir de sanal adresi var. Gerçek adresleri kod içerisinde değişkenlere erişirken, sanal adresleri ise register ları sınıflandırarak dışarıdan erişim için kullanıyorum.
"Aslanlar kendi hikayelerini yazmadıkça, avcıların kahramanlık hikayelerini dinlemek zorundayız."

Tagli

@quarko hocam, yani bir çeşit sözlük (dictionary) oluşturuyorsun sanırım. Doğru mu anlamışım?

Alıntı yapılan: Elektroemre - 25 Kasım 2018, 10:09:02Bence en güzeli C++ gösterimi ama union içinde untagged struct da diğer bir seçenek olabilir.
Hocam union benim de aklıma geldi. Pratik bir çözüm olurdu ama struct'takine benzer bir şekilde yazımın güzel gözükmesi için en dışarıda isimsiz union gerekirdi sanırım ve bunu da extern edemezdik galiba. Gerçi emin de değilim, C'nin böyle uç konuları kafamı karıştırıyor...
Gökçe Tağlıoğlu