Modbus
Attention, sur les CX8090, il faut acheter la licence lors de l'achat (plus simple)
Les sources sous http://libmodbus.org/download
Download de la dernière version 3.1.1 sous ~weber/src/modbus
Instalation: unzip, tar xvf, cd libmodbus-3.1.1, ./configure, make et sous root: make install
tout va sous
- /usr/local/lib (libmodbus.so, libmodbus.la?, pgkconfig?)
- /usr/local/include/modbus (modbus.h et modbus-*.h)
- /usr/local/include/libmodbus (modbus.h (identique à modbus/modbus.h))
Mode d'emploi sous http://libmodbus.org/site_media/html/libmodbus.html
Le synopsis est:
#include <modbus.h>
cc `pkg-config --cflags --libs libmodbus` files
en gros inclure /usr/local/lib dans le LD_LIBRARY_PATH (rem: ce n'était pas le cas et j'ai modifié nos scripts de démarrage sous /etc/envv.d/40t120.[c]sh et sous ~weber/src/administration/t4_beta.csh etc...
La libraire fonctionne parfaitement bien, je l'ai également installée sur les LCU car il tournent en 32bits
ZONE MEMOIRE
Comme exemple: ~weber/src/spectro_src/libspePLC.c ou ~weber/src/modbus
La zone mémoire accessible en R/W est située en 0x3000 pour le CX8090 (pas facile de la trouver en suivant la doc Beckhoff), et en 0x4000 sur le BC9120 (doc Greg). Donc prévoir une variable pour la baseMemory si un programme doit etre portable.
Mefiance: la mémoire est persistante même au changement de projet. Il est donc préférable de faire un Reset ou Reset All pour ces tests et pour les variable "Forced" un un Release Force.
Egalement les valeurs d'intialisation (lors de la déclaration des variables) n'est mise en place qu'après un Reset ainsi si l'on souhaite de nouvelles valeurs d'initialisation, il faut absolument faire un Reset.
ALIGNEMENT MEMOIRE
Sur la PLC les adresses sont comptées en BYTE et par exemple l'adresse en %Mx0 (avec x==X,B,W,D) équivaut à l'adresse 0x3000 (voir remarques plus haut).
La PLC compte en adresse byte: 1, 2, 3 ..
- Les BYTES 8 bits s'alignent normalement sur ces adresses: %MB0, %MB1, %MB3, ...
- Les WORDs 16 bits doivent s'aligner sur les adresses paires: %MW0, %MW2, %MW4, ...
- Les DWORDS 32 bits doivent s'aligner sur les les adresses quadruples: %MD0, %MD4, %MD8, ...
- Les LREAL 64 bits doivent s'aligner sur les adresses octuples : %MD0, %MD8, %MD16, ...
En cas de melange de type le compilateur laisse des zones mémoires inoccupées.
Remarque: les temps (ex: t#1s) sont codés sur 32bit non signé en [ms]
Le compilateur informe sur le désalignement mais pas sur l'overlapping. L'overlapping ne semble pas etre une erreur, plusieurs variables peuvent pointer sur le meme espace mémoire.
Le test de l'overlapping se fait avec: TC-PLC-Control->Project->Check->Overlapping memory Areas
Les exemples suivants fonctionnent sous Linux 32 et 64bits.
EXEMPLES
Exemple en C pour lire 10 word(16 bits) mémoire:
[weber@argos1 modbus]$ more testzero.c #include #include main(){ modbus_t *mbTdf; struct timeval response_timeout; uint16_t tab_reg[64]; int rc; int i; int setpoint; mbTdf = modbus_new_tcp("10.10.132.61", MODBUS_TCP_DEFAULT_PORT); if(mbTdf == NULL){ fprintf(stderr, "modbus_new_tcp: can not create Context TCP/IP"); exit(-1); } if (modbus_connect(mbTdf) == -1) { fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno)); modbus_free(mbTdf); exit(-1); } printf("Connected\n"); modbus_set_debug(mbTdf, TRUE); printf("Offset 0x3000 --------READ--------------------\n"); rc = modbus_read_registers(mbTdf, 0x3000, 10, tab_reg); if (rc == -1) { fprintf(stderr, "%s\n", modbus_strerror(errno)); return -1; } for (i=0; i < rc; i++) { printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]); } modbus_close(mbTdf); modbus_free(mbTdf); printf("Connection closed\n"); }
Exemple avec Boolean:
a AT %MB0 : BOOL; b AT %MB1 : BOOL; c AT %MB2 : BOOL; d AT %MB3 : BOOL; a := FALSE; b := TRUE; c := TRUE; d := FALSE; Donne: reg[0]=256 (0x100) reg[1]=1 (0x1)
Exemple avec des Byte. Attention, rappel, il faut faire un Reset car il sont intialisés au démarrage uniquement
a AT %MB0 : BYTE := 16#10; b AT %MB1 : BYTE := 16#20; c AT %MB2 : BYTE := 16#30; d AT %MB3 : BYTE := 16#40; Donne: reg[0]=8208 (0x2010) reg[1]=16432 (0x4030)
a AT %MB0 : BYTE := 16#10; b AT %MB1 : BYTE := 16#20; c AT %MB2 : BYTE := 16#30; d AT %MW4 : WORD := 16#DDFF; Donne: reg[0]=8208 (0x2010) reg[1]=16432 (0x4030) <-- il reste le 0x40 de l'exemple precedent reg[2]=56831 (0xDDFF) Reset et Reset All n'efface pas 0x40 Apres un On/Off de la PLC on a: reg[0]=8208 (0x2010) reg[1]=48 (0x30) reg[2]=56831 (0xDDFF)
Un peu plus complexe avec des variables de type TIME
a AT %MB0 : BYTE := 16#10; b AT %MB1 : BYTE := 16#20; c AT %MB2 : BYTE := 16#30; d AT %MW4 : WORD := 16#DDFF; e AT %MD8 : TIME := t#1s; f AT %MD12: TIME := t#23s456ms; g AT %MD16: TIME := t#65s535ms; h AT %MD20: TIME := t#65s536ms; i AT %MW24: WORD := 16#AABB Donne: reg[0]=8208 (0x2010) reg[1]=48 (0x30) <== byte padding added reg[2]=56831 (0xDDFF) reg[3]=0 (0x0) <== word padding added reg[4]=1000 (0x3E8) <== 1[s]000[ms] reg[5]=0 (0x0) reg[6]=23456 (0x5BA0) <== 23[s]456[ms] reg[7]=0 (0x0) reg[8]=65535 (0xFFFF) <== 65[s]535[ms] reg[9]=0 (0x0) reg[10]=0 (0x0) <== 65[s]536[ms] reg[11]=1 (0x1) reg[12]=52445 (0xCCDD)
L'idée est de récupérer un ensemble de valeurs en un seul appel.
- les chaines de caractères: char|STRING (longueur == multiple de 8 (64bits))
- les reels double précision: double|LREAL (64bits)
- les reels simple precision ou entier: float|REAL, int|DINT,UDINT,DWORD (32bits)
- les entiers 16 bits: short|INT,UINT,WORD (16bits)
- les bytes: char|SINT,USINT,BYTE (8bits)
Attention: selon les machines 32 ou 64bits en C certain types ont une définitions différente:
Ainsi il ne faut pas utiliser le type long (4bytes sur les machines 32bits et 8bytes sur les machines 64bits), mais toujours utiliser le type int (4bytes)
main(){ modbus_t *mbTdf; int rc; struct plcMemStruct { unsigned char byte1; unsigned char byte2; unsigned char byte3; unsigned char bytePAD; unsigned short short1; unsigned short shortPAD; unsigned int time1; unsigned int time2; unsigned int time3; unsigned int time4; unsigned short short2; } plcMem; ... printf("Offset 0x3000 --------READ---STRUCTURE----------------\n"); rc = modbus_read_registers(mbTdf, 0x3000, sizeof(plcMem), (uint16_t *)&plcMem); if (rc == -1) { fprintf(stderr, "%s\n", modbus_strerror(errno)); return -1; } printf("byte1=%d (0x%X)\n", plcMem.byte1, plcMem.byte1); printf("byte2=%d (0x%X)\n", plcMem.byte2, plcMem.byte2); printf("byte3=%d (0x%X)\n", plcMem.byte3, plcMem.byte3); printf("short1=%d (0x%X)\n", plcMem.short1, plcMem.short1); printf("time1=%d (0x%X)\n", plcMem.time1, plcMem.time1); printf("time1=%d (0x%X)\n", plcMem.time2, plcMem.time2); printf("time1=%d (0x%X)\n", plcMem.time3, plcMem.time3); printf("time1=%d (0x%X)\n", plcMem.time4, plcMem.time4); printf("short2=%d (0x%X)\n", plcMem.short2, plcMem.short2); ... } Resultat: byte1=16 (0x10) byte2=32 (0x20) byte3=48 (0x30) short1=56831 (0xDDFF) time1=1000 (0x3E8) time1=23456 (0x5BA0) time1=65535 (0xFFFF) time1=65536 (0x10000) short2=52445 (0xCCDD)
Ecriture de structure
plcMem.byte1++; plcMem.byte2++; plcMem.byte3++; plcMem.short1++; plcMem.time1++; plcMem.time2++; plcMem.time3++; plcMem.time4++; plcMem.short2++; rc = modbus_write_registers(mbTdf, 0x3000, sizeof(plcMem), (uint16_t *)&plcMem); if (rc == -1) { fprintf(stderr, "%s\n", modbus_strerror(errno)); return -1; }
A PROPOS DES DATES
Ce sont tous des entiers 32 bits non signés:
Type PLC | Exemple | Definition | Limites | Type C |
---|---|---|---|---|
DATE | D#2014-02-01 | temps unix UT [s] à cette date à 00H00M00S | 0=1/1/1970-0:0:0 | unsigned integer 32 bits |
TIME_OF_DAY | TOD#20:12:34:56.789 | temps du jour [ms] |
(mini) 00:00:00.000=0 (maxi) 23:59:59.999 = 86399999 |
unsigned integer 32 bits |
DATE_AND_TIME | DT#2014-02-01:-12:34:56 | temps unix UT [s] à cette date à l'heure donnée | unsigned integer 32 bits |
Par exemple dans le code PLC on a: date AT %MD48 : DATE; Le code C aura: #define BASE 0x3000 #define PLC(add) (BASE+(int)(ceil(add/2.))) ... unsigned int date; ... rc = modbus_read_registers(mbTdf, PLC(48), sizeof(date), (unin16_t *)&date);