Forum Programmation.c Pb de lecture port série (RS232 + transceiver RS485)

Posté par . Licence CC by-sa.
Tags : aucun
0
9
juil.
2018

Bonjour,

Sur une plateforme Compute Module 3 avec un transceiver RS232=>RS485, je dois interroger un équipement en Modbus.
Entre l'écriture et la lecture je dois piloter une sortie (GPIO 12) pour activer la lecture au l’écriture du transceiver.
Avec le code ci dessous j'obtiens une réponse incomplète et très aléatoire. Soit pour la réponse réelle 01 03 04 17 12 03 21 9F 6A j'obtiens :
03 04 17 12 03 21 9F 6A
ou
17 12 03 21 9F 6A
et quelques fois rien

On dirait que la lecture ne se déclenche pas assez rapidement après l'écriture et le pilotage du transceiver.

Est-ce un problème de configuration ou peut-être la façon de faire ?

D'avance merci

gcc -Wall -o serial serial.c -lwiringPi

Voici le code :

#include <sys/resource.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>  /* File Control Definitions          */
#include <termios.h>/* POSIX Terminal Control Definitions*/
#include <unistd.h> /* UNIX Standard Definitions         */
#include <errno.h>  /* ERROR Number Definitions          */
#include <wiringPi.h> /*pilotage du GPIO */

int main(int argc, char **argv)
{

        //Test Priorité de l'application :
        int which = PRIO_PROCESS;
        id_t pid;
        int ret;
        int priority = -20;

        pid = getpid();
        ret = getpriority(which, pid);
        ret = setpriority(which, pid, priority);
        ret = getpriority(which, pid);
        printf("Priorité = %i\n",ret);


        //Ouverture et configuration du port
        int fd;
        fd = open("/dev/ttyAMA0",O_RDWR | O_NOCTTY);
        //printf("fd = %i\n",fd);
        if (fd == -1) printf("Pb d'ouverture du périphérique\n");
        else {
                printf("Ouverture du port OK\n");
                /*---------- Setting the Attributes of the serial port using termios structure --------- */
                struct termios SerialPortSettings;      /* Create the structure                          */
                tcgetattr(fd, &SerialPortSettings);     /* Get the current attributes of the Serial port */
                /* Setting the Baud rate */
                cfsetispeed(&SerialPortSettings,B9600); /* Set Read  Speed as 9600                       */
                cfsetospeed(&SerialPortSettings,B9600); /* Set Write Speed as 9600                       */
                /* 8N1 Mode */
                SerialPortSettings.c_cflag |= PARENB;   /*EVEN.... Disables the Parity Enable bit(PARENB),So No Parity   */
                SerialPortSettings.c_cflag &= ~CSTOPB;   /* CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit */
                SerialPortSettings.c_cflag &= ~CSIZE;    /* Clears the mask for setting the data size             */
                SerialPortSettings.c_cflag |=  CS8;      /* Set the data bits = 8                                 */
                SerialPortSettings.c_cflag &= ~CRTSCTS;       /* No Hardware flow Control                         */
                SerialPortSettings.c_cflag |= CREAD | CLOCAL; /* Enable receiver,Ignore Modem Control lines       */
                SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);          /* Disable XON/XOFF flow control both i/p and o/p */
                SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /* Non Cannonical mode                            */
                SerialPortSettings.c_oflag &= ~OPOST;/*No Output Processing*/
                /* Setting Time outs */
                SerialPortSettings.c_cc[VMIN] = 0; /* Read at least 10 characters */
                SerialPortSettings.c_cc[VTIME] = 5; /* Wait 500ms   */

                if((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) /* Set the attributes to the termios structure*/
                        printf("\n  ERROR ! in Setting attributes");
                else
                        printf("Configuration OK\n");

                /*------------------------------- Write data to serial port -----------------------------*/
                //Activation de l'ecriture sur le transceivers RS485 => PIN 12 :ON

                wiringPiSetup ();
                pinMode (12, OUTPUT);
                digitalWrite (12, 1);

                char write_buffer [] = "\x01\x03\x40\x00\x00\x02\xD1\xCB";
                //char write_buffer [] = "\x01\x03\x30\x20\x00\x02\xCA\xC1";    /* Buffer ForwActiv   */
                //char write_buffer [] = "\x01\x03\x30\x40\x00\x02\xCA\xDF";      /* Buffer RevActiv   */
                //char write_buffer [] = "\x01\x03\x20\x00\x00\x02\xCF\xCB";      /* Buffer Voltage   */
                int  bytes_written  = 0;        /* Value for storing the number of bytes written to the port */

                bytes_written = write(fd,write_buffer,sizeof(write_buffer));/* use write() to send data to port                                            */
                                                                     /* "fd"                   - file descriptor pointing to the opened serial port */
                                                                     /* "write_buffer"         - address of the buffer containing data              */
                                                                     /* "sizeof(write_buffer)" - No of bytes to write                               */
                //printf("\n  %s ecrit sur le périphérique",write_buffer);
                printf("\n  %d octets ecrit sur le périphérique\n", bytes_written);

                //Activation de la lecture sur le transceiver RS485 => PIN 12 :OFF
                //delay (90);   /test delai
                if (tcdrain(fd) != 0){
                        perror("tcdrain() error");
                }
                digitalWrite (12, 0);

                /*------------------------------- Read data from serial port -----------------------------*/
                tcflush(fd, TCIFLUSH);   /* Discards old data in the rx buffer            */
                char read_buffer[32];   /* Buffer to store the data received              */
                int  bytes_read = 0;    /* Number of bytes read by the read() system call */
                int i = 0;

                bytes_read = read(fd,&read_buffer,8); /* Read the data                   */
                printf("Bytes Rxed -%d\n", bytes_read); /* Print the number of bytes read */
                for(i=0;i<bytes_read;i++)        /*printing only the received characters*/
                    printf("%x",read_buffer[i]);

                close(fd);
                printf("\nfermeture du port\n");
        }

return 0;
}
  • # tcflush

    Posté par . Évalué à 3.

    C'est probablement la ligne tcflush qui fait perdre les premiers octets.

    tcflush(fd, TCIFLUSH); /* Discards old data in the rx buffer */

    Tu devrais la supprimer.

    Rappel de l'extrait de la man page :

    tcflush() discards data written to the object referred to by fd but not
    transmitted, or data received but not read, depending on the value of
    queue_selector:

    TCIFLUSH
    flushes data received but not read.

  • # Complèments

    Posté par . Évalué à 1.

    Bonjour,
    J'ai essayé sans le tcflush…
    Mais il n'y a aucun changement.

    Pour info : avec un convertisseur USB/RS485 sur Windows et Easymodbus la com fonctionne très bien…

    • [^] # Re: Complèments

      Posté par . Évalué à 2.

      En plus du flush à faire non pas après mais avant l'envoi de la trame (conseillé pour éviter que ton buffer soit pollué par d'anciennes trames ou autre), la question qui se pose est le temps de retournement de ton interface de communication:

      digitalWrite (12, 0);

      Ne connaissant pas ces fonctions, je ne peux pas te répondre sur le type/temps d'exécution de ces fonctions. Sont-elles synchrones/asynchrones?
      Quel est le temps de retournement du transceiver?

      avec un analyseur logique (ou oscillo), tu verrais les signaux et ce serait plus facile à débuggeur: tu verrais peut-être que ton signal de commande arrive au transceiver alors que ton équipement a déjà commencé à répondre…

      • [^] # Re: Complèments

        Posté par . Évalué à 2. Dernière modification le 10/07/18 à 15:56.

        Avec le flush avant l'envoi idem.

        Concernant le temps d'application du "digitalWrite", j'ai vérifié à l'oscillo et il est fluctuant (0/+10ms) mais toujours appliqué avant la trame de réponse de l'équipement.
        Concernant le temps de retournement du transceiver (ISL83085E), c'est de l'analogique donc très inférieur à la ms.

        Voici un graphe l'oscillo (mes diff sur modbus) de mon appli et celui du easymodub (com fonctionnelle à chaque fois). Il n'y a pas de grandes différences à part la fin de la trame d'envoi qui repasse à l'état initial un peu plus tard car lié au pilotage du transceiver (0/+10ms).

        Je penche plus pour une mauvaise configuration soft mais peut-être que le hard est en cause.

        De mon appli :
        DLFP

        De Easy modbus :
        DLFP

        • [^] # Re: Complèments

          Posté par . Évalué à 2.

          Avec le flush avant l'envoi idem.

          Oui, c'est normal, ce n'est pas ça qui changera quelque chose, c'est juste histoire que le buffer soit propre.

          tes oscillogrammes, sont bien pris entre le transceiver et la raspberry? ou sur la 485?… et oui, peut-être faut-il aller gratter du côté du sw…

          dans ta config, j'ai l'impression que tu actives la parity (PARENB) alors que le commentaire dit le contraire…

          • [^] # Re: Complèments

            Posté par . Évalué à 1.

            Voici la mesure coté RS485 (jaune) et UART RX CM3 (violet) :
            DLFP

            Pour moi CM3 reçoit bien la bonne trame.

            Effectivement les commentaires sont faux (copier/coller dsl) mais la config de communication(EVEN) parait bonne car l'équipement répond. Si je le modifie il ne répond plus.
            Est-ce un problème d’interprétation de bit de start de la trame de réponse ?
            Si c'est un problème de timing soft à la lecture du buffer, comment le visualiser ?

            • [^] # Re: Complèments

              Posté par . Évalué à 2. Dernière modification le 11/07/18 à 13:10.

              La trame de retour est nickel, on voit bien les premiers octets 0x01, 0x03, 0x04 (héhé, l'habitude du décodage en live de traffic uart!…)

              En cherchant un peu sur le net, j'ai vu que tu n'étais pas le premier à qui celà est arrivé:
              C language program reading UART loses characters

              il faut "disabler" getty dans inittab… lis l'ensemble du post, ça devrait t'éclairer et peut-être résoudre le problème.

              • [^] # Re: Complèments

                Posté par . Évalué à 1.

                Effectivement j'ai "agetty" qui tourne par défaut.
                lorsque que je le désactive :

                sudo systemctl stop serial-getty@ttyAMA0.service

                Et que je lance mon programme, la trame est bien envoyé, l'équipement répond mais l'application se met en attente lors du "read". J'ai essayé plusieurs configurations des VMIN et VTIME sans succès.

                EDIT :
                Pour que mon appli fonctionne il faut masquer "aggety" :

                sudo systemctl mask serial-getty@ttyAMA0.service

                Et miracle… Les trames sont lus à chaque coup !

                Un grand MERCI à a2g pour m'avoir mis sur la voie.

                Bonne continuation

                • [^] # Re: Complèments

                  Posté par . Évalué à 1.

                  Par contre comment l'appliquer (le mask) par défaut au démarrage du système ?

                  • [^] # Re: Complèments

                    Posté par . Évalué à 1.

                    Super que tu tu aies trouvé une solution!
                    pour le démarrage, on dirait que l'init manager est systemd.
                    Comme je n'ai pas encore utilisé du systemd sur mes plateformes, je ne peux que t'aiguiller sur la recherche: créer un service dans /etc/systemd/system qui lancera un script où tu mettra tes commandes… Il y a des exemples un peu partout sur le net, et avec un peu de chance il y a déjà ce genre de questions sur le forum! ;-)

                    ou bien, de lancer la commande juste avant le lancement de ton application.

                  • [^] # Re: Complèments

                    Posté par (page perso) . Évalué à 4.

                    La commande mask est permanente. Il n'y a donc pas besoin de l'appliquer au démarrage du système.

                    « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.