电子技术应用|技术阅读
登录|注册

您现在的位置是:电子技术应用 > 技术阅读 > 教你动手写TCP上位机与小熊派通信

教你动手写TCP上位机与小熊派通信

背景

  • 关于上位机的文章,作者在之前就分享过好几个上位机的开发流程分享。如下表:
序号内容语言
1C#
2C#
3QT
4python
5QT
6QT
  • 上位机开发不限于语言,找我之前开发中,初衷就是那种方便就使用那种语言开发,如:C#, QT, python, VB等。

  • 本篇文章分享是采用QT开发的TCP上位机,功能:通过TCP上位机控制小熊派板载外设。

  • 上位机采用QT开发,小熊派跑RT-Thread,如下图为总体框图。

TCP上位机

  • 本上危机支持作为服务器也支持作为客户端,可以通过按键进行切换到不同的模式。该上位机主要功能:①控制板子LED,②调节扩展板E53_IA1上LED的亮度。
  • 不过属于是作为客户端还是服务器都可以实现上述两个功能。
  • 上位机功能实现主要有两个文件:bearpi.cpp和bearpi.h

TCP上位机开发说明:

  • 在项目文件中添加如下内容:
  • QT       += network
  • TCP网络编程需要用到的头文件:
  • #include <QTcpSocket>
    #include <QTcpServer>
  • bearpi.h头文件内容说明:申明了界面的事件槽函数,并且定义了Tcp_Server和TcpSocket的句柄。
  • #ifndef BEARPI_H
    #define BEARPI_H

    #include <QMainWindow>
    #include <QTcpSocket>
    #include <QTcpServer>

    QT_BEGIN_NAMESPACE
    namespace Ui { class bearpi; }
    QT_END_NAMESPACE

    class bearpi : public QMainWindow
    {
        Q_OBJECT

    public:
        bearpi(QWidget *parent = nullptr);
        ~bearpi();


    private slots:
        void on_mode_pushButton_clicked();

        void on_start_pushButton_clicked();

        void on_open_pushButton_clicked();

        void on_pwm_horizontalSlider_valueChanged(int value);

    private:
        void switch_mode();
        void new_client_connect();

    private:
        Ui::bearpi *ui;
        QTcpServer *server;
        QTcpSocket *socket;
    };
    #endif // BEARPI_H
  • bearpi.cpp源文件构造函数内容说明:①实例化Tcp_Server和TcpSocket的句柄,②定义IP地址的lineEdit控件格式,③根据模式使能对应的控件。
  • bearpi::bearpi(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::bearpi)
    {
        ui->setupUi(this);

        server = new QTcpServer();
        socket = new QTcpSocket();

        QRegExp ip_RegExp("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$");
        QRegExpValidator *ip_format = new QRegExpValidator(ip_RegExp, this);
        ui->ip_lineEdit->setValidator(ip_format);

        switch_mode();
    }
  • bearpi.cpp源文件switch_mode()函数内容说明:①根据模式使能对应的控件。
  • void bearpi::switch_mode()
    {
        if(ui->mode_pushButton->text() == tr("SERVER"))
        {
            ui->ip_lineEdit->setEnabled(false);
        }
        else if(ui->mode_pushButton->text() == tr("CLIENT"))
        {
            ui->ip_lineEdit->setEnabled(true);
        }
    }
  • bearpi.cpp源文件new_client_connect()函数内容说明:①当模式作为server时,有客户端请求建立连接时的信号槽函数,主要与客户端建立socket句柄。
  • void bearpi::new_client_connect()
    {
        socket = server->nextPendingConnection();
        qDebug() << "A Client connect!";
    }
  • bearpi.on_start_pushButton_clicked()函数内容说明:①当作为服务器,则进行建立服务器,并联建立客户端连接槽函数。①当作为客户端,根据IP地址和端口号与服务器建立连接。
  • void bearpi::on_start_pushButton_clicked()
    {
        if(ui->start_pushButton->text() == tr("START"))
        {
            if(ui->mode_pushButton->text() == tr("SERVER"))
            {
                int port = 0;

                connect(server,&QTcpServer::newConnection,this,&bearpi::new_client_connect);

                port = ui->port_lineEdit->text().toInt();

                if(!server->listen(QHostAddress::Any, port))
                {
                    qDebug() << server->errorString();
                    return;
                }
                qDebug() << "Listen successfully";
            }
            else if(ui->mode_pushButton->text() == tr("CLIENT"))
            {
                QString ip;
                int port = 8080;

                ip = ui->ip_lineEdit->text();
                port = ui->port_lineEdit->text().toInt();

                socket->abort();
                socket->connectToHost(ip, port);

                if(!socket->waitForConnected(3000))
                {
                    qDebug() << "Connect failed";
                    return;
                }
                qDebug() << "Connect successfully";
            }
            ui->start_pushButton->setText(tr("STOP"));
            ui->mode_pushButton->setEnabled(false);
        }
        else if(ui->start_pushButton->text() == tr("STOP"))
        {
            if(ui->mode_pushButton->text() == tr("SERVER"))
            {
                socket->abort();
                server->close();

            }
            else if(ui->mode_pushButton->text() == tr("CLIENT"))
            {
                socket->disconnectFromHost();
            }
            ui->start_pushButton->setText(tr("START"));
            ui->mode_pushButton->setEnabled(true);
        }
    }
  • bearpi.cpp源文件on_open_pushButton_clicked()函数内容说明:①控制小熊派板载LED的开关的槽函数,②通过发送led_open和led_close字符串来控制板载LED。
  • void bearpi::on_open_pushButton_clicked()
    {
        qDebug() << ui->open_pushButton->text();
        if(ui->open_pushButton->text() == tr("OPEN LED"))
        {
            socket->write("led_open");
            socket->flush();
            ui->open_pushButton->setText(tr("CLOSE LED"));
        }
        else if(ui->open_pushButton->text() == tr("CLOSE LED"))
        {
            socket->write("led_close");
            socket->flush();
            ui->open_pushButton->setText(tr("OPEN LED"));
        }
    }
  • bearpi.cpp源文件on_pwm_horizontalSlider_valueChanged()函数内容说明:①调节扩展板E53_IA1上LED的亮度的槽函数,②根据滑动条的值调节扩展板E53_IA1上LED的PWM。
  • void bearpi::on_pwm_horizontalSlider_valueChanged(int value)
    {
        char pwm_str[1] = {0};

        pwm_str[0] = (char)value;

        socket->write(pwm_str);
        socket->flush();

        qDebug() << pwm_str[0];
    }

    TCP上位机演示:

    1. 作为server:2. 作为client:

    小熊派开发

    • 为了快速的开发,我直接采用rt-thread,它提供的丰富的组件,不用自己造轮子。小熊派上的功能主要是启动一个client,然后通过接收到上位机的命令之后通过PWM或GPIO控制LED。
    • 网络:采用小熊派板载模组ESP8266。使用RT-Thread的AT组件,SAL组件,netdev组件。
    • PWM:使用了PWM设备驱动框架
    • demo中小熊派作为客户端,TCP上位机作为服务器。上位机通过TCP控制小熊派。

    小熊派代码说明:

  • 通过RT-THREAD强大的组件,使我们编程更加统一简单。
    • 创建一个socket,然后连接到对应上位机服务器。
    • 根据设备名获取PWM的句柄,然后初始化pwm的初始值并使能。
    • 创建一个线程,用于处理服务器下发的指令及数据。
    void bearpi_client(int argc, char **argv)
    {
      int ret;
      struct hostent *host;
      struct sockaddr_in server_addr;

      host = gethostbyname(IP_ADDR);

      if ((sock_client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
      {
        rt_kprintf("Socket error\n");
        return;
      }

      server_addr.sin_family = AF_INET;
      server_addr.sin_port = htons(PORT);
      server_addr.sin_addr = *((struct in_addr *)host->h_addr);
      rt_memset(&(server_addr.sin_zero), 0sizeof(server_addr.sin_zero));

      if (connect(sock_client_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
      {
        rt_kprintf("Connect fail!\n");
        closesocket(sock_client_fd);
        return;
      }

      pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);

      rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);

      rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);

      rt_thread_init(&bearpi_thread,
                    "bearpi",
                    bearpi_entry,
                    RT_NULL,
                    &bearpi_stack[0],
                    sizeof(bearpi_stack),
                    105);
      rt_thread_startup(&bearpi_thread);

      return;
    }
    MSH_CMD_EXPORT(bearpi_client, a tcp client sample);
  • 接受数据线程:根据命令的类型进行调节板载的LED。
  • void bearpi_entry(void* paramenter)
    {
      char recv_data[BUFSZ] = {0};
      uint32_t recv_len = 0;

      uint32_t is_open = 0;
      while (1)
      {
        recv_len = recv(sock_client_fd, recv_data, BUFSZ - 10);

        if(recv_len > 0)
        {
          if(strncmp(recv_data, "led_open"8) == 0)
          {
            rt_pin_write(LED0_PIN, PIN_HIGH);
            is_open = RT_TRUE;
          }
          else if(strncmp(recv_data, "led_close"1) == 0)
          {
            rt_pin_write(LED0_PIN, PIN_LOW);
            is_open = RT_FALSE;
            rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, 0);
          }
          else
          {
            if(is_open == RT_TRUE)
            {
              pulse = period / 100 * recv_data[0];
              rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
            }
          }
        }
      }
    }

    吐槽说明:

    • 在RT-THREAD的驱动中,有一个做的不是很友好的地方如下图:
    • drv_pwm.c中了为了划分不同的型号进行的归类,RT-THREAD值进行大类归类,而没有进行细化不同的型号。如L4系列,但实际STM32L431并没有那么多TIM,所以当我们使能PWM这个功能时,会编译不过。。
    • 所以这里需要细分不同型号,但在作者工程中,把报错的地方直接注释了。

    整体功能演示

    源代码厂库

    • 代码链接:https://gitee.com/RiceChen0/bearpi_rt-thread.git

    • 分支:tcp_demo

    • 如果你们觉得不错,记得加个:Star。