Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

Porta seriale in C linux

Linguaggi e sistemi

Moderatori: Foto UtentePaolino, Foto Utentefairyvilje

0
voti

[1] Porta seriale in C linux

Messaggioda Foto Utentedaniele1996 » 5 mag 2025, 11:58

Ciao a tutti, sto realizzando un esempio di comunicazione con un micro tramite porta seriale, in pratica il micro riceve byte per byte e dopo il timeout dell'ultimo carattere invia indietro la stringa, cercando su internet ho trovato diversi siti che spiegano come scrivere del codice per far funzionare una porta seriale e così ho scritto una prima bozza di programma:
Codice: Seleziona tutto
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>


// /dev/ttyUSB0


int main(){
   int serial_port = open("/dev/ttyUSB0", O_RDWR);
     struct termios tty;

     if(tcgetattr(serial_port, &tty) != 0) {
         printf("Error from tcgetattr\n");
         return 1;
     }

     tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity
     tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication
     tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size
     tty.c_cflag |= CS8; // 8 bits per byte
     tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control
     tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

     tty.c_lflag &= ~ICANON;
     tty.c_lflag &= ~ECHO; // Disable echo
     tty.c_lflag &= ~ECHOE; // Disable erasure
     tty.c_lflag &= ~ECHONL; // Disable new-line echo
     tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
     tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
     tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

     tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes ( newline chars)
     tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed

     tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
     tty.c_cc[VMIN] = 0;

     // Set baud rate 9600/*
     cfsetispeed(&tty, B9600);
     cfsetospeed(&tty, B9600);

     // Save tty settings
     if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
         printf("Error  from tcsetattr\n");
         return 1;
     }

     // Write to serial port
     unsigned char messaggio[] = { 'H', 'e', 'l', 'l', 'o', '\r' };
     write(serial_port, messaggio, sizeof(messaggio));

     // Allocate memory for read buffer, set size according to your needs
     char read_buf [256];
     memset(read_buf, '\0', sizeof(read_buf));

     sleep(1);
     //int bytes;
     //ioctl(serial_port, FIONREAD, &bytes);
     //printf("Bytes %d\n",bytes);
     //sleep(1);
     // Read bytes. The behaviour of read() (e.g. does it block?,
     // how long does it block for?) depends on the configuration
     // settings above, specifically VMIN and VTIME

     int num_bytes = read(serial_port, &read_buf, sizeof(read_buf));
     // n is the number of bytes read. n may be 0 if no bytes were received, and can also be -1 to signal an error.
     if (num_bytes < 0) {
         printf("Error reading");
         return 1;
     }

     printf("Read %d bytes. Received message: %s\n", num_bytes, read_buf);

     close(serial_port);

   return 0;
}

Il codice funziona, però se tolgo lo sleep(1) legge soltanto il primo carattere, non posso impostare il VMIN perché la lunghezza dei dati trasmessi può variare e a parte questo la funzione read ritorna al raggiungimento dei caratteri impostati, non aspetta la fine del timeout che indica la fine della stringa, in pratica dovrei affidarmi al VTIME che dovrebbe essere il parametro di attesa dall'ultimo carattere ricevuto che però sembra non funzionare, ho provato anche con valori più alti ma la stringa si ferma sempre al primo carattere. Nel programma che devo realizzare lo sleep(1) non posso usarlo... Manca qualcosa?
Avatar utente
Foto Utentedaniele1996
610 3 8 11
Sostenitore
Sostenitore
 
Messaggi: 1554
Iscritto il: 29 ago 2011, 11:29

1
voti

[2] Re: Porta seriale in C linux

Messaggioda Foto Utentetheking0 » 5 mag 2025, 12:33

Ciao, in pratica vuoi leggere fino a quando non arrivano più dati per un certo intervallo di tempo (cioè un timeout dopo l'ultimo carattere ricevuto), senza conoscere a priori la lunghezza del messaggio. Questo comportamento non è supportato direttamente dalla combinazione VMIN/VTIME.

Tieni conto che il VTIME non è un timeout "inter-byte", cioè non resetta il timer ad ogni byte ricevuto. Funziona solo come un timeout assoluto quando VMIN = 0.

Quindi, se c'è almeno un byte disponibile, read() ritorna subito, ignorando VTIME.

Devi implementarlo manualmente con un ciclo che:

Legge byte per byte (o blocchi piccoli).
Aspetta un po’ tra le letture successive.
Se non riceve più niente per n decimi di secondo, considera il messaggio concluso.

Una cosa del genere:

Codice: Seleziona tutto
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <sys/time.h>

#define SERIAL_PORT "/dev/ttyUSB0"
#define MAX_BUFFER 256
#define READ_TIMEOUT_MS 500  // Inter-byte timeout (millisecondi)

long current_millis() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (tv.tv_sec * 1000L) + (tv.tv_usec / 1000L);
}

int main() {
    int serial_port = open(SERIAL_PORT, O_RDWR | O_NOCTTY);
    if (serial_port < 0) {
        perror("open");
        return 1;
    }

    struct termios tty;
    if (tcgetattr(serial_port, &tty) != 0) {
        perror("tcgetattr");
        close(serial_port);
        return 1;
    }

    // Serial config
    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;
    tty.c_cflag &= ~CRTSCTS;
    tty.c_cflag |= CREAD | CLOCAL;

    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
    tty.c_oflag &= ~(OPOST | ONLCR);

    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 1; // Tentativo di timeout breve per non bloccare a lungo la read()

    cfsetispeed(&tty, B9600);
    cfsetospeed(&tty, B9600);

    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
        perror("tcsetattr");
        close(serial_port);
        return 1;
    }

    // Invio messaggio
    const unsigned char message[] = { 'H', 'e', 'l', 'l', 'o', '\r' };
    write(serial_port, message, sizeof(message));

    // Lettura con timeout inter-byte manuale
    char read_buf[MAX_BUFFER];
    int total_bytes = 0;
    long last_byte_time = current_millis();

    while (1) {
        char byte;
        int n = read(serial_port, &byte, 1);
        if (n > 0) {
            read_buf[total_bytes++] = byte;
            last_byte_time = current_millis();

            if (total_bytes >= MAX_BUFFER)
                break;
        } else {
            long now = current_millis();
            if (now - last_byte_time > READ_TIMEOUT_MS) {
                break; // Nessun byte ricevuto da troppo tempo
            }
            usleep(10000); // 10 ms
        }
    }

    read_buf[total_bytes] = '\0'; // Terminazione stringa
    printf("Read %d bytes. Received message: %s\n", total_bytes, read_buf);

    close(serial_port);
    return 0;
}


Se non riceve dati per READ_TIMEOUT_MS millisecondi, assume che il messaggio sia finito, quindi inserisci il valore che ti sembra più consono al tuo caso.

Hai delle esigenze particolari per aver scelto il C ?
Te lo chiedo perché ti semplificherebbe la vita (e non poco) farlo in python.
Avatar utente
Foto Utentetheking0
1.442 1 6 11
Master
Master
 
Messaggi: 605
Iscritto il: 11 feb 2012, 22:37

0
voti

[3] Re: Porta seriale in C linux

Messaggioda Foto Utentedaniele1996 » 5 mag 2025, 13:12

Ah ok, allora in pratica quello che ho fatto nel micro (impostare un timer e avviarlo/resettarlo alla ricezione di ogni carattere) devo farlo anche sul programma che gira sul computer,
theking0 ha scritto:Hai delle esigenze particolari per aver scelto il C ?
Te lo chiedo perché ti semplificherebbe la vita (e non poco) farlo in python.

Si, tra le varie applicazioni devo realizzare una comunicazione con un modulo gsm simcom sim900a che appunto usa la porta seriale per comunicare... Questo modulo richiede un programma che gestisce rubrica, messaggi ricevuti che possono contenere informazioni criptate, comandi da inviare ad un webserver da me sviluppato in C per gestire alcune aree del programma e comandi su processi associati, quindi mi serve realizzare un programma proprio in C da avviare come processo figlio sul webserver... il python non l'ho mai usato, però penso che in questo caso possa essere un problema far interagire i due programmi scritti in liguaggi differenti. Provo ad impostare il codice che hai postato, Grazie.
Avatar utente
Foto Utentedaniele1996
610 3 8 11
Sostenitore
Sostenitore
 
Messaggi: 1554
Iscritto il: 29 ago 2011, 11:29

0
voti

[4] Re: Porta seriale in C linux

Messaggioda Foto UtenteNoNickName » 5 mag 2025, 13:32

theking0 ha scritto:Te lo chiedo perché ti semplificherebbe la vita (e non poco) farlo in python.


E qualsiasi altro linguaggio di alto livello, considerata la bassa velocità di comunicazione.
Il modo giusto sarebbe utilizzare i pin predisposti, cioè RTS/CTS, DTR, ecc... oppure in alternativa usare dei byte di controllo
Avatar utente
Foto UtenteNoNickName
311 1 2 5
Stabilizzato
Stabilizzato
 
Messaggi: 333
Iscritto il: 10 nov 2021, 14:54

0
voti

[5] Re: Porta seriale in C linux

Messaggioda Foto Utentedaniele1996 » 5 mag 2025, 14:14

Funziona, questo ovviamente è un esempio di prova, successivamente metterò delle strutture dati di configurazione,thread di ricezione, liste di trasmissione/ricezione, una domanda, visto che si parla di queste linee
NoNickName ha scritto: RTS/CTS, DTR, ecc...

in che modo si possono utilizzare? tipo per esempio se collego un max485, questo integrato richiede un pin digitale per impostarlo in trasmissione o ricezione, è possibile usare queste uscite?

I byte di controllo sinceramente mi sembrano problematici, consideriamo il caso si stiano ricevendo delle informazioni che possono non essere semplici stringhe ma in genere valori compresi tra 0 e 255, ad un certo punto il programma riceve un byte che interpretato come di "terminazione" chiude la lettura dei dati, questo può essere un problema, certo, se si tratta di semplici stringhe dove sono presenti solo lettere e numeri sicuramente può andare bene
Avatar utente
Foto Utentedaniele1996
610 3 8 11
Sostenitore
Sostenitore
 
Messaggi: 1554
Iscritto il: 29 ago 2011, 11:29

1
voti

[6] Re: Porta seriale in C linux

Messaggioda Foto UtenteNoNickName » 5 mag 2025, 14:45

daniele1996 ha scritto:in che modo si possono utilizzare? tipo per esempio se collego un max485, questo integrato richiede un pin digitale per impostarlo in trasmissione o ricezione, è possibile usare queste uscite?



No, scusa, l'RS485 non prevede RTS/CTS/DTR.
Pensavo che avevi una RS232.

daniele1996 ha scritto:I byte di controllo sinceramente mi sembrano problematici


Ci sono diverse application note di molti produttori di microprocessori per risolvere questo problema, che si presenta spesso quando fai il DFU over Serial. Io l'ho implementato in vb.net per caricare il firmware su un PIC32.
In alternativa, puoi inserire in posizione nota un byte che specifichi la lunghezza del payload. Anche questa è una soluzione già in uso in alcuni protocolli, come per esempio il Meterbus.
Avatar utente
Foto UtenteNoNickName
311 1 2 5
Stabilizzato
Stabilizzato
 
Messaggi: 333
Iscritto il: 10 nov 2021, 14:54

0
voti

[7] Re: Porta seriale in C linux

Messaggioda Foto Utentedaniele1996 » 5 mag 2025, 16:08

Sto usando un covertitore USB - seriale TTL (PL2303), nel retro della scheda ci sono dei pad con quelle sigle, ora collegando un max485 a questo convertitore occorre un uscita digitale per impostare se il transreceiver deve essere in trasmissione o in ricezione... magari sfruttando quelle uscite di controllo si potrebbe fare...
Avatar utente
Foto Utentedaniele1996
610 3 8 11
Sostenitore
Sostenitore
 
Messaggi: 1554
Iscritto il: 29 ago 2011, 11:29

0
voti

[8] Re: Porta seriale in C linux

Messaggioda Foto UtenteNoNickName » 5 mag 2025, 17:16

daniele1996 ha scritto:magari sfruttando quelle uscite di controllo si potrebbe fare...


Certo. Sempre che il tuo terminale trasmittente li supporti.
Avatar utente
Foto UtenteNoNickName
311 1 2 5
Stabilizzato
Stabilizzato
 
Messaggi: 333
Iscritto il: 10 nov 2021, 14:54

0
voti

[9] Re: Porta seriale in C linux

Messaggioda Foto Utentedaniele1996 » 5 mag 2025, 19:39

infatti volevo includere il controllo di queste uscite nel programma, guardando il pinout della seriale, oltre ai pin di TX e RX ci sono quattro ingressi e due uscite, ho trovato come far funzionare le uscite:
Codice: Seleziona tutto
int RTS_flag,DTR_flag;

RTS_flag = TIOCM_RTS;   /* Modem Constant for RTS pin */
DTR_flag = TIOCM_DTR;   /* Modem Constant for DTR pin */

ioctl(fd,TIOCMBIC,&RTS_flag);   // RTS = 0
ioctl(fd,TIOCMBIS,&RTS_flag);   // RTS = 1


ioctl(fd,TIOCMBIC,&DTR_flag);   // DTR = 0
ioctl(fd,TIOCMBIS,&DTR_flag);   // DTR = 1

Ancora non l'ho provato, ma gli ingressi? Dove posso trovare qualche esempio magari impostando qualche interrupt routine su questi ingressi?
Avatar utente
Foto Utentedaniele1996
610 3 8 11
Sostenitore
Sostenitore
 
Messaggi: 1554
Iscritto il: 29 ago 2011, 11:29

0
voti

[10] Re: Porta seriale in C linux

Messaggioda Foto UtenteNoNickName » 5 mag 2025, 20:30

E' sufficiente RTS e CTS. Se cerchi hardware flow control su internet troverai tanti esempi in tanti linguaggi diversi compreso c e c++
Avatar utente
Foto UtenteNoNickName
311 1 2 5
Stabilizzato
Stabilizzato
 
Messaggi: 333
Iscritto il: 10 nov 2021, 14:54


Torna a PC e informatica

Chi c’è in linea

Visitano il forum: Nessuno e 7 ospiti