背景
- 关于上位机的文章,作者在之前就分享过好几个上位机的开发流程分享。如下表:
| 序号 | 内容 | 语言 |
|---|---|---|
| 1 | 《如何定制自己的HID调试助手》 | C# |
| 2 | 《C# 串口上位机开发》 | C# |
| 3 | 《Qt 串口上位机开发》 | QT |
| 4 | 《教你动手写UDP协议栈 - OTA上位机》 | python |
| 5 | 《基于RT-THREAD nano的平衡车--上位机软件》 | QT |
| 6 | 《R-Plan上位机》 | QT |
- 上位机开发不限于语言,找我之前开发中,初衷就是那种方便就使用那种语言开发,如: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), 0, sizeof(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), 10, 5); 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 - 1, 0); 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。
关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/72548.html