La programmation en série RS485
J'ai été chargé de la mise en œuvre du protocole ModBus sur une RS485 2 fils système. (En fait, c'est trois fils, A/B et GND).
ModBus n'est pas la question, mais l'étape avant que...simple je/S sur l'interface.
Je suis en utilisant le FTDI USB-RS485 convertisseur pour connecter un hôte Linux (non interchangeable) à un ordinateur hôte Windows (interchangeable avec un autre hôte Linux, mais j'aimerais éviter)
L'encodage est censé être 19200, 8, n, 1.
Mais il ne semble tout simplement pas de travail.
Je n'ai pas le code exact à portée de main, mais sur Linux, je suis en train de faire ceci:
int fd = open("/dev/ttyS3", O_RDWR | O_CTTY);
if(fd == -1) return "Error while opening the port";
Ensuite, j'ai configurer le port.
struct termios tty;
tcgetattr(fd, &tty);
cfsetispeed(&tty, B19200);
cfsetospeed(&tty, B19200);
tty.c_cflag = CS8; //Empties the cflags and sets the character width.
tty.c_cflag |= (CLOCAL | CREAD); //Sets 'recommended' options.
tty.c_lflag = 0;
tty.c_iflag = 0;
tty.c_oflag = 0;
tcgetattr(fd, TCSANOW, &tty);
De la parité et de Contrôle de Flux sont actuellement pas prévu, puisque le résultat final sera vous connecter à un faible niveau, où j'ai besoin de prendre soin des signaux de moi-même. En outre, il n'y a pas de fils, ce qui permettrait de "sans entrave de la communication". (Après je ne veux pas d'un XON/XOFF personnage à la limite de la plage d'octets que je peux transmettre)
Toutes ces fonctions vont bien et les données est définie.
Sur Windows, j'ai ouvert le port série comme ceci:
DCB SP;
HANDLE hSerial = CreateFile("COM6", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(hSerial == INVALID_HANDLE_VALUE) return "Error while opening the port";
GetCommState(hSerial, &SP);
La parité est désactivé, ainsi que le contrôle de flux. Taille en octets est fixé à 8.
Edit:
Depuis, il a été demandé, voici mon code pour le débit en bauds sur Windows (de mémoire)
SP.DCBlength= sizeof(SP);
SP.BaudRate = 19200;
SP.La parité = NOPARITY;
SP.Bits d'arrêt = ONESTOPBIT;
SetCommState(hSerial, &SP);
Encore une fois, toutes ces fonctions s'exécutent parfaitement.
Maintenant, pour le cas de test qui me donne un mal de tête important.
Sur l'hôte Linux, j'ai créer un octet de la mémoire tampon de 256 octets taille.
Ce tampon est rempli avec le caractère des valeurs de 0 à 255...et puis envoyées sur le fil de l'écriture.
Dans le même temps, l'autre côté est en attente avec "ReadFile" pour les données arrivent.
Avec cette configuration, pour 'les autres hôte Linux", ainsi que pour l'hôte Windows, 256 Octets arriver...mais c'est PAS le nombres de 0 à 255, mais quelque chose de 00 06 etc.
Je peux obtenir le Linuxhost au travail, quand je suis le réglage de tous les membres de la structure termios à 0 avant de paramétrer les options que je veux réellement. Je devine, c'est parce que les caractères de contrôle,...mais si je fais ça, l'hôte Windows soit ne reçoit que 4 de 256 octets.
Comme je l'ai dit, malheureusement je n'ai pas le code à portée de main. Si quelqu'un a une idée de quel point j'ai pu le résoudre, je lui en serais très reconnaissant. Je vais poster plus de code, une fois que j'ai à nouveau accès.
Comment je me suis mise en œuvre de l'opération de lecture:
DWORD nBytes = 0;
char Buffer[256], *ptr = Buffer;
int Rem = 256;
while(Rem) {
ReadFile(hSerial, ptr, Rem, &nBytes, 0);
Rem -= nBytes;
ptr += nBytes;
}
//Evaluate Buffer
À noter, je n'ai mis les délais d'attente, mais ne me souviens pas les valeurs exactes.
Edit: Depuis que j'ai maintenant accès à mon lieu de travail, encore une fois, c'est l' (actuel) du code.
const char *InitCOM(const char *TTY) {
struct termios tty;
hSerial = open(TTY, O_RDWR | O_NOCTTY | O_NDELAY);
if(hSerial == -1) return "Opening of the port failed";
fcntl(hSerial, F_SETFL, 0);
if(tcgetattr(hSerial, &tty) != 0) return "Getting the parameters failed.";
if(cfsetispeed(&tty, B19200) != 0 || cfsetospeed(&tty, B19200) != 0) return "Setting the baud rate failed.";
//CFlags
//Note: I am full aware, that there's an '=', and that it makes the '&=' obsolete, but they're in there for the sake of completeness.
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; //8-bit characters
tty.c_cflag |= (CLOCAL | CREAD);und erlaubt 'Lesen'.
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
//Input Flags
tty.c_iflag &= ~IGNBRK;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
//Local Flags
tty.c_lflag = 0;
//Output Flags
tty.c_oflag = 0;
//Control-Characters
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 5;
if(tcsetattr(hSerial, TCSAFLUSH, &tty) != 0) return "Setting the new parameters failed";
return NULL;
}
Que pour l'envoi/la réception de code:
int main(int argc, char* argv[]) {
#if defined FOR_PC
const char *err = InitCOM("/dev/ttyUSB0");
#else
const char *err = InitCOM("/dev/ttyS3");
#endif
if(err) printf("Error while initalizing: %s ErrNum: %d\n", err, errno);
else {
/*unsigned char C[256]; //Original code with the array
int nBytes;
#ifdef FOR_PC
int Rem = 256, ReqCount = 0;
unsigned char *ptr = C;
while(Rem > 0) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
nBytes = read(hSerial, ptr, Rem);
if(nBytes > 0) {
Rem -= nBytes;
ptr += nBytes;
++ReqCount;
}
}
printf("Number of received Bytes: %d in %d sends.\n\n", 256 - Rem, ReqCount);
for(int i = 0; i < 256; ++i) {
printf("%02X ", C[i]);
if((i%32) == 31) printf("\n");
}
#else
for(int i = 0; i < 256; ++i) C[i] = i;
nBytes = write(hSerial, C, 256);
printf("\nWritten Bytes: %d\n", nBytes);
#endif*/
//Single-Byte Code
unsigned char C = 0x55;
#ifdef FOR_PC
while(true) { //Keeps listening
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
read(hSerial, &C, 1);
printf("Received value 0x%02X\n", C);
}
#else
write(hSerial, &C, 1); //Sends one byte
#endif
close(hSerial);
}
return 0;
}
Comme de l'Oscilloscope: j'ai testé les deux directions à l'envoi. Les deux ont fait leur travail tout à fait admirable.
Le signal de 0x55 est une constante de Haut/vers le Bas à la longueur de 50 microsecondes (comme il se doit, le réglage de la vitesse de transmission est plus un problème).
Donc, il y a quelque chose dans mon "recevoir" code que j'ai fais de mal? Est le 'select' mal?
Merci de poster le code de Windows configurer le débit en bauds et de l'appel de SetCommState.
Là vous allez.
'4' est toujours suspect numéro sur les systèmes 32 bits, (sizeof(char*)).
La chose est, ReadFile() a une fâcheuse tendance à revenir plus tôt, n'ayant pas lu autant d'octets que vous souhaitez. C'est pourquoi il a " le lpNumberOfBytesRead paramètre, de sorte que vous pouvez savoir combien de votre tampon il a lu ce temps et, si nécessaire, émettre une autre ReadFile ().
OriginalL'auteur ATaylor | 2012-09-21
Vous devez vous connecter pour publier un commentaire.
Oui, je suis le réglage de la vitesse de transmission sur windows. Je suis aussi assez sûr, que le câble fonctionne (sinon, le Linux exemple avec à la fois la structure définie à 0 avant de définir le débit en bauds et les choses ne fonctionnent pas, serait-il?) Malheureusement, je n'ai pas RS232 disponible.
Vous avez vraiment, vraiment besoin d'utiliser un oscilloscope lorsque vous faites de matériel liés à la programmation de ce type. Parce que pour l'instant vous ne savez pas si c'est le Linux côté, les vitres latérales ou le matériel de causer des ennuis. Si vous pouviez mesurer un bon UART signal de données avec le bon débit en bauds de quitter la machine Linux, alors le problème est dans le récepteur, sinon c'est l'émetteur ou le matériel. Vous ne pouvez pas supposer que la version Linux est correct parce qu'il peut parler "avec lui-même", parce que vous pouvez ensuite régler le débit et le format des données à rien et il faudra encore travailler.
Je ne suis, je me considère comme 99% des logiciels guy. Mais encore, je considère un oscilloscope à être un outil obligatoire pour la programmation embarquée.
yup, Pas de scopee, aucun workee'.
OriginalL'auteur unwind
Votre fonction de lecture peut facilement exploser. Si vous êtes près de la fin de la mémoire tampon, et vous pouvez les lire plus que le montant de la remplir, vous permettra de copier-delà de la fin de la mémoire tampon, remplacer la pile.
Sur le Linux de l'envoi de côté, vous devriez penser à "raw", par exemple, cfmakeraw(). De cette façon, vous ne serez pas dérangé par le système de vous "aider" (comme l'ajout de CR lorsque vous envoyez un newline -- vis les données binaires...). Il y a un moyen de le faire microsoft, mais j'ai oublié comment.
OriginalL'auteur bobwki
sur windows côté, avez-vous mis le DCBLength champ dans votre DCB struct?
ah ok, je vous l'ai dit pour définir DCBlength simplement parce que c'est un très commun piège quand il s'agit de série I/O sur Windows 🙂 j'ai foiré avec le DCB configuration de la structure d'un nombre incalculable d'heures...
OriginalL'auteur Gianluca Ghettini