使用 C 对串行 AT 命令的响应较慢,但使用 minicom 响应速度较快

Slow response to serial AT commands using C, but fast response with minicom

我在使用我编写的 C++ 脚本从 Beaglebone Black 上的串口读取时遇到问题。该脚本向 Adafruit FONA GSM/GPS 设备发送命令并从中接收响应并正常工作,除了发送命令和实际从设备接收任何字节之间有很长的延迟(我必须延迟 1 秒在写入和读取命令之间,以便从设备获得响应)。但是,当我使用 minicom 串行终端仿真器时,在发送命令和接收响应之间没有明显的延迟。我猜这与我打开串口的方式有关,但我不知道我还能改变什么。现在我将设置设置为原始输入和输出模式,没有线路控制或回声,但仍然无法减少响应时间。非常欢迎和赞赏任何帮助或想法!谢谢!

CPP 文件:

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/ioctl.h>
#include <linux/serial.h>
#include <termios.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <stdio.h>
#include "Fona_control.h"


Fona_control::Fona_control(void)
{
    begin();
}

void Fona_control::get_gps(void)
{
    printf("Reading GPS\n");
    unsigned char gpsbuff[250];
    memset(gpsbuff,'[=10=]',250);
    sleep(0.1);

    int bytes_a = 0;
    int n_write = write(fona_fd,GPS_GET_DATA,sizeof(GPS_GET_DATA)-1);
    sleep(1);
    ioctl(fona_fd,FIONREAD,&bytes_a);
    printf("Bytes avail: %i\n",bytes_a);
    int n_read = read(fona_fd,gpsbuff,bytes_a);
    printf("Buffer: %s\n",gpsbuff);
    printf("Bytes read: %i\n",n_read);

    return;
}



void Fona_control::begin(void)
{
    printf("FONA Beginning\n");

    struct termios oldtio, newtio;
    struct serial_struct serinfo;
    // Load the pin configuration
    /* Open modem device for reading and writing and not as controlling tty
    because we don't want to get killed if linenoise sends CTRL-C. */
    fona_fd = open(FONA_DEVICE, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fona_fd < 0) { perror(FONA_DEVICE); exit(-1); }

    bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */

    /*  BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
    CRTSCTS : output hardware flow control (only used if the cable has
              all necessary lines. See sect. 7 of Serial-HOWTO)
    CS8     : 8n1 (8bit,no parity,1 stopbit)
    CLOCAL  : local connection, no modem contol
    CREAD   : enable receiving characters */
    cfsetspeed(&newtio,BAUDRATE_Fona);
    newtio.c_cflag |= ( CLOCAL | CREAD );
    newtio.c_cflag &= ~CSIZE;
    newtio.c_cflag |= CS8;
    newtio.c_cflag &= ~PARENB;   
    newtio.c_cflag &= ~CRTSCTS;
    newtio.c_cflag &= ~CSTOPB;
    newtio.c_cc[VMIN]  = 1;
    newtio.c_cc[VTIME] = 1;

ioctl (fona_fd, TIOCGSERIAL, &serinfo);
serinfo.flags |= 0x4000;
ioctl (fona_fd, TIOCSSERIAL, &serinfo);

    /* setup for non-canonical mode */
    //newtio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | ISTRIP | INLCR | INPCK | ICRNL | IXON | IGNCR);  
    newtio.c_iflag = 0; 

    /* Set line flags */ 
    //newtio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    newtio.c_lflag = 0; 

    /*  Raw output
    newtio.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | ONOEOT| OFILL | OLCUC | OPOST); */
    newtio.c_oflag = 0;


    /* now clean the modem line and activate the settings for the port */
    tcflush(fona_fd, TCIOFLUSH);
    if(tcsetattr(fona_fd,TCSANOW,&newtio) < 0)
    {
        printf("Error Setting FONA Serial Port Attributes!");
        exit(0);
    };

    /* terminal settings done, now send FONA initialization commands*/

    sleep(1);
    unsigned char buffer[50];
    int bytes_avail = 0;
    int n_write     = 0;
    int n_read      = 0;
    int cnt         = 0;
    memset(buffer,'[=10=]',50);
    tcflush(fona_fd, TCIOFLUSH);
    while(strstr((char *)buffer,"OK") == NULL && cnt < 5)
    {
        memset(buffer,'[=10=]',50);
        n_write = write(fona_fd,FONA_AT,sizeof(FONA_AT)-1);
        sleep(1);
        ioctl(fona_fd,FIONREAD,&bytes_avail);
        printf("BA: %i\n",bytes_avail);
        if(bytes_avail > 0)
        {
            n_read = read(fona_fd,buffer,bytes_avail);
            printf("%s\n",buffer);
        }
        sleep(1);
        cnt++;
    }

    sleep(1);
    n_write = write(fona_fd,"+++",3);
    bytes_avail = 0;
    sleep(1);
    ioctl(fona_fd,FIONREAD,&bytes_avail);
    printf("BA2: %i\n",bytes_avail);
    n_read = read(fona_fd,buffer,bytes_avail);
    printf("%s",buffer);

    printf("AT Accepted\n");
    sleep(1);
    tcflush(fona_fd, TCIOFLUSH);
    unsigned char buffer1[50];
    memset(buffer1,'[=10=]',50);

    int n = write(fona_fd,FONA_ECHO_OFF,sizeof(FONA_ECHO_OFF)-1);
    printf("Writ: %i\n",n);
    bytes_avail = 0;
    sleep(1);
    ioctl(fona_fd,FIONREAD,&bytes_avail);
    printf("BA2: %i\n",bytes_avail);
    n = read(fona_fd,buffer1,bytes_avail);
    printf("%s",buffer1);
    memset(buffer1,'[=10=]',50);
    sleep(1);


    n = write(fona_fd,GPS_POWER_ON,sizeof(GPS_POWER_ON)-1);
    printf("Writ: %i\n",n);
    bytes_avail = 0;
    sleep(1);
    ioctl(fona_fd,FIONREAD,&bytes_avail);
    printf("BA2: %i\n",bytes_avail);
    n = read(fona_fd,buffer1,bytes_avail);
    printf("%s\n",buffer1);
    memset(buffer1,'[=10=]',50);
    sleep(1);

    n = write(fona_fd,FONA_SMS_TYPE,sizeof(FONA_SMS_TYPE)-1);
    printf("Writ: %i\n",n);
    bytes_avail = 0;
    sleep(1);
    ioctl(fona_fd,FIONREAD,&bytes_avail);
    printf("BA2: %i\n",bytes_avail);
    n = read(fona_fd,buffer1,bytes_avail);
    printf("%s\n",buffer1);
    sleep(1);

}

H 文件:

#ifndef _fona_control_H
#define _fona_control_H

#include <stdint.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>

#define FONA_DEVICE "/dev/ttyO5" //Beaglebone Black serial port
//#define _POSIX_SOURCE 1 /* POSIX compliant source */

#define BAUDRATE_Fona B115200   // Change as needed, keep B

/* Define FONA AT Commands */
#define FONA_AT         "AT\r\n"
#define FONA_ECHO_OFF       "ATE0\r\n"
#define FONA_CMD_REPEAT     "A/\r\n"
#define FONA_NO_ECHO        "ATE0\r\n"
#define FONA_PIN_CHECK      "AT+CPIN?\r\n"
#define FONA_PIN_SEND       "AT+CPIN=1234\r\n"
#define FONA_SMS_TYPE       "AT+CMGF=1\r\n"


/* Define FONA GPS AT Commands */
#define GPS_POWER_ON    "AT+CGNSPWR=1\r\n"
#define GPS_POWER_OFF   "AT+CGNSPWR=0\r\n"
#define GPS_GET_DATA    "AT+CGNSINF\r\n"

/* Define FONA GPS NMEA Commands */
#define PMTK_CMD_HOT_START  "AT+CGNSCMD=0,"$PMTK101*32"\r\n"
#define PMTK_CMD_WARM_START "AT+CGNSCMD=0,"$PMTK102*31"\r\n"
#define PMTK_CMD_COLD_START     "AT+CGNSCMD=0,"$PMTK103*30"\r\n"
#define PMTK_SET_NMEA_5HZ       "AT+CGNSCMD=0,"$PMTK220,200*2C"\r\n"
#define PMTK_SET_BAUD_38400     "AT+CGNSCMD=0,"$PMTK251,38400*27"\r\n"
#define PMTK_SET_WAAS       "AT+CGNSCMD=0,"$PMTK301,2*2E"\r\n"
#define PMTK_SET_SBAS       "AT+CGNSCMD=0,"$PMTK313,1*2E"\r\n"
#define PMTK_NMEA_TYPES         "AT+CGNSCMD=0,"$PMTK314,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0*29"\r\n"
#define PMTK_STANDY_MODE    "AT+CGNSCMD=0,"$PMTK161,0*28"\r\n"    //Send any byte to exit standby mode


class Fona_control {
   public:

      void begin(void);
      void get_gps(void);
      Fona_control(void); // Constructor when using HardwareSerial

      uint8_t fix_status, fix_mode, sats, sats_used, glo_sats, cn0;
      uint8_t month, day, minute;
      uint32_t year;

      double seconds, latitude, longitude, speed, course, hdop, vdop, pdop, hpa, vpa;

      int fona_fd;

   private:


};


#endif

1 秒睡眠是致命的,我可以向您保证 minicom 不会这样做。它将等待数据进来,而不是轮询,然后显示它。串行数据很慢。以 9600 波特率发送的每个字符大约需要一毫秒才能到达。在 115.2k 波特率下,您将在不到 85 微秒的时间内移动一个角色。另一方面,您的 Beaglebone 以纳秒为单位工作,因此如果您在读取之前不等待,则数据还不会出现。也就是说,您应该在不到 1 毫秒的时间内完成 "OK",而等待整整一秒就太过分了。

考虑阻塞并等待 "OK"

所需的三个字节
while(strstr((char *)buffer,"OK") == NULL && cnt < 5)
{
    memset(buffer,'[=10=]',50);
    n_write = write(fona_fd,FONA_AT,sizeof(FONA_AT)-1);
    n_read = read(fona_fd,buffer,3);
    cnt++;
}

如果设备从不响应,这可能会永远阻塞,因此您需要超时

我能想到的最简单的包罗万象的超时机制是这样的:

int retry= 5;
while (retry)
{
    fd_set readfs;    // file descriptor set used by select
    struct timeval timeout;
    FD_ZERO(&readfs); // clear file descriptor set 
    FD_SET(fona_fd, &readfs); //  set our port as the one item in the set
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    if (select(fona_fd+1, &readfs, NULL, NULL, &timeout) !=0)
    {
        rval = read(fona_fd, buffer, sizeof(buffer)-1); 
        // will stop reading after configurable gap between bytes read
        buffer[rval] = '[=11=]'; // NULL terminate the buffer or it ain't a string
                             // If not a string, results of strstr are undefined
        if (strstr((char *)buffer,"OK") != NULL) 
        {
            break;
        }
    }
    retry--;
}

Documentation on select. It's very very cool function. Not as cool as epoll, 但更容易找到教程。如果你做了很多 selectepoll 好好看看。

Select 在这种情况下将在放弃之前等待数据 1 秒。如果找到数据,程序将进入 if 正文并且 read 将读取,直到自从接收到最后一个字符或缓冲区已满以来经过了用户可配置的字符次数。

然后我们用 null 终止缓冲区中的数据,以便我们可以在其上使用字符串处理例程。这也消除了 memset 缓冲区的需要。如果缓冲区包含 "OK",我们将退出循环。

否则我们会减少重试计数器并循环查看是否有更多重试。