Qt串口基本设置与协议收发
前言
1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下
2.comboBox没有点击的信号,所以做了一个触发的功能
3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊
4.给自己挖个坑,下一期做一个查看波形的上位机
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
串口功能
波特率设置
串口开关
串口异常检测
字符串/HEX收发
定时发送
接收数据分隔
协议组包
协议拆包
源代码
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QtSerialPort/QSerialPort> // 提供访问串口的功能
#include <QtSerialPort/QSerialPortInfo> // 提供系统中存在的串口信息
#include <QTime>
#include <QtCharts>
#include <QTimer>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECT//用于表格设置enum FieldColNum {colTime,colCmd1,colCmd2,colData,};enum CellType {ctTime,ctCmd1,ctCmd2,ctData,};
public:int tableRowCnt = 0;
public:Widget(QWidget *parent = nullptr);~Widget();void serialPortInit(); //串口初始化void windowInit(); //显示窗口初始化void refreshCom(); //刷新串口void tableInit(); //表格初始化void createItemsARow(int rowNum, QByteArray *protocalData); //表格新建一行QString ByteArrayToHexString(QByteArray &ba);
private:Ui::Widget *ui;QSerialPort* serialPort;
public slots:void comboBoxClicked(); //comboBox被点击
private slots:void sendData(); //发送串口数据void receiveData(); //接收串口数据void openSerialport(); //串口开启void closeSerialport(); //串口关闭void setBuad(int); //设置波特率void clearRcv(); //清楚接收缓存void on_btnConvert_clicked(); //转换按钮被点击void on_btnClear_clicked(); //清楚转化的按钮被点击void sendProtocalHexData(); //以hex格式发送串口数据void handleSerialError(QSerialPort::SerialPortError serialPortErr); //串口异常捕获
};#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "newcombobox.h"
#include <algorithm>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//创建一个定时器用来定时发送QTimer *timer1 = new QTimer();timer1->start(1000);connect(timer1,&QTimer::timeout,[=](){int timed = ui->comboBox_timedSend->currentText().toInt();timer1->start(timed);if(ui->checkBox_timedSend->isChecked() == true){sendData();}});windowInit();tableInit();serialPort = new QSerialPort();serialPortInit();connect(serialPort,SIGNAL(readyRead()),this,SLOT(receiveData()));connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));connect(ui->pushButton_sendData,SIGNAL(clicked()),this,SLOT(sendData()));connect(ui->pushButton_openSerialPort,SIGNAL(clicked()),this,SLOT(openSerialport()));connect(ui->pushButton_closeSerialPort,SIGNAL(clicked()),this,SLOT(closeSerialport()));connect(ui->comboBox_chooseCom,SIGNAL(clicked()),this,SLOT(comboBoxClicked()));connect(ui->pushButton_clearRcv,SIGNAL(clicked()),this,SLOT(clearRcv()));connect(ui->pushButton_convert,SIGNAL(clicked()),this,SLOT(on_btnConvert_clicked()));connect(ui->pushButton_clearConvertData,SIGNAL(clicked()),this,SLOT(on_btnClear_clicked()));connect(ui->pushButton_sendProtocalData,SIGNAL(clicked()),this,SLOT(sendProtocalHexData()));connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));connect(ui->comboBox_setBuad,SIGNAL(activated(int)),this,SLOT(setBuad(int)));
}Widget::~Widget()
{delete ui;
}//串口异常捕获
void Widget::handleSerialError(QSerialPort::SerialPortError serialPortErr)
{if(serialPortErr == QSerialPort::ResourceError){QMessageBox::critical(NULL, "critical", "设备拔出", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);closeSerialport();}if(serialPortErr == QSerialPort::DeviceNotFoundError){QMessageBox::critical(NULL, "critical", "找不到串口", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);closeSerialport();}
}void Widget::comboBoxClicked()
{refreshCom();
}void Widget::windowInit()
{ui->pushButton_closeSerialPort->setEnabled(false);ui->pushButton_openSerialPort->setEnabled(false);ui->pushButton_sendData->setEnabled(false);setWindowTitle(tr("串口收发"));ui->comboBox_timedSend->addItem("10");ui->comboBox_timedSend->addItem("100");ui->comboBox_timedSend->addItem("1000");ui->comboBox_timedSend->setCurrentIndex(2);
}void Widget::refreshCom()
{//显示串口列表ui->comboBox_chooseCom->clear();foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts())ui->comboBox_chooseCom->addItem(portInfo.portName()+":"+portInfo.description());ui->pushButton_openSerialPort->setEnabled(ui->comboBox_chooseCom->count()>0); //
}void Widget::serialPortInit(){refreshCom();ui->comboBox_setBuad->addItem("9600");ui->comboBox_setBuad->addItem("115200");ui->comboBox_setBuad->addItem("921600");ui->comboBox_setBuad->setCurrentIndex(1);}void Widget::tableInit()
{QStringList headerText;headerText<<"时间"<<"命令1"<<"命令2"<<"数据";ui->tableWidget->setColumnCount(headerText.size()); //设置表格列数ui->tableWidget->resizeColumnsToContents();for (int i=0;i<ui->tableWidget->columnCount();i++){QTableWidgetItem *headerItem=new QTableWidgetItem(headerText.at(i));QFont font=headerItem->font(); //获取原有字体设置font.setBold(true); //设置为粗体font.setPointSize(10); //字体大小headerItem->setForeground(QBrush(Qt::black)); //设置文字颜色headerItem->setFont(font); //设置字体ui->tableWidget->setHorizontalHeaderItem(i,headerItem); //设置表头单元格的item}ui->tableWidget->setColumnWidth(0, 60);ui->tableWidget->setColumnWidth(1, 50);ui->tableWidget->setColumnWidth(2, 50);ui->tableWidget->setColumnWidth(3, 250);
}//为一行的单元格创建 Items
void Widget::createItemsARow(int rowNum, QByteArray *protocalData)
{uchar preFix = 0xA5;uchar crc = 0;uchar temp = 0;temp = static_cast<uchar>(protocalData->at(0));if(static_cast<uchar>(protocalData->at(0)) == preFix){for(int i=1; i<protocalData->length()-2; i++){temp = static_cast<uchar>(protocalData->at(i));crc += static_cast<uchar>(protocalData->at(i));}temp = static_cast<uchar>(protocalData->at(protocalData->length()-2));if(crc != static_cast<uchar>(protocalData->at(protocalData->length()-2))){return;}uchar len = protocalData->at(1);uchar cmd1 = protocalData->at(2);uchar cmd2 = protocalData->at(3);QByteArray data = protocalData->mid(4,len-6);QDateTime curTime = QDateTime::currentDateTime();//获取系统现在的时间QString time = curTime.toString("hh:mm:ss"); //设置显示格式uint8_t str1 = static_cast<uint8_t>(cmd1);QString hexStr1 = QString("%1").arg(str1, 2, 16, QLatin1Char('0')).toUpper();uint8_t str2 = static_cast<uint8_t>(cmd2);QString hexStr2 = QString("%1").arg(str2, 2, 16, QLatin1Char('0')).toUpper();QString testdata = ByteArrayToHexString(data).toLatin1().toUpper();QTableWidgetItem *item = new QTableWidgetItem(time, ctTime);ui->tableWidget->setItem(rowNum, colTime, item);item = new QTableWidgetItem(hexStr1, ctCmd1);ui->tableWidget->setItem(rowNum, colCmd1, item);item = new QTableWidgetItem(hexStr2, ctCmd2);ui->tableWidget->setItem(rowNum, colCmd2, item);item = new QTableWidgetItem(testdata, ctData);ui->tableWidget->setItem(rowNum, colData, item);}auto lastRowIndex = ui->tableWidget->rowCount()-1; // 最后一行的索引auto lastModelIndex = ui->tableWidget->model()->index(lastRowIndex, 0);ui->tableWidget->scrollTo(lastModelIndex); // 滚动到最后一行
}QString Widget::ByteArrayToHexString(QByteArray &ba)
{QDataStream out(&ba,QIODevice::ReadWrite); //将str的数据 读到out里面去QString buf;while(!out.atEnd()){qint8 outChar = 0;out >> outChar; //每次一个字节的填充到 outcharQString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0')).toUpper() + QString(" "); //2 字符宽度buf += str;}return buf;
}void Widget::sendData()
{QString message = ui->lineEdit_sendData->text();if(ui->checkBox_hexSend->isChecked() == true){serialPort->write(QByteArray::fromHex(message.toLatin1()));}else{serialPort->write(message.toLatin1());}
}void Widget::receiveData()
{QByteArray message;QString hexMsg;message.append(serialPort->readAll());QDateTime time = QDateTime::currentDateTime(); //获取系统现在的时间QString date = time.toString("hh:mm:ss"); //设置显示格式if(ui->checkBox_hexRcv->isChecked() == true){tableRowCnt++;ui->tableWidget->setRowCount(tableRowCnt);createItemsARow(tableRowCnt-1,&message);hexMsg = ByteArrayToHexString(message).toLatin1();ui->textEdit_RecData->append(date+QString("-> ")+hexMsg.toUpper());}else{ui->textEdit_RecData->append(date+QString("-> ")+message);}
}
void Widget::openSerialport()
{ui->pushButton_closeSerialPort->setEnabled(true);ui->pushButton_openSerialPort->setEnabled(false);QList<QSerialPortInfo> comList = QSerialPortInfo::availablePorts();QSerialPortInfo portInfo = comList.at(ui->comboBox_chooseCom->currentIndex());serialPort->setPort(portInfo); //设置使用哪个串口if(serialPort->open(QIODevice::ReadWrite) == false){QMessageBox::critical(NULL, "critical", "找不到串口/串口被占用", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);closeSerialport();}else{serialPort->setBaudRate(QSerialPort::Baud115200);serialPort->setDataBits(QSerialPort::Data8);serialPort->setParity(QSerialPort::NoParity);serialPort->setStopBits(QSerialPort::OneStop);serialPort->setFlowControl(QSerialPort::NoFlowControl);ui->pushButton_sendData->setEnabled(true);}
}void Widget::closeSerialport()
{if(serialPort->isOpen()){serialPort->clear();serialPort->close();}ui->pushButton_closeSerialPort->setEnabled(false);ui->pushButton_openSerialPort->setEnabled(true);
}void Widget::setBuad(int buad)
{QString str = ui->comboBox_setBuad->currentText();serialPort->setBaudRate(str.toInt());
}void Widget::clearRcv()
{ui->textEdit_RecData->clear();
}void Widget::on_btnClear_clicked()
{ui->lineEdit_protocalData->clear();
}void Widget::on_btnConvert_clicked()
{ui->lineEdit_protocalData->clear();bool ok;QString str = "A5";int val1= ui->lineEditCmd1->text().toInt(&ok,16); //以十六进制数读入QString str1 = QString("%1").arg(val1, 2, 16, QLatin1Char('0'));int val2= ui->lineEditCmd2->text().toInt(&ok,16); //以十六进制数读入QString str2 = QString("%1").arg(val2, 2, 16, QLatin1Char('0'));if ((str1.isEmpty())||(str2.isEmpty()))return;int val3= ui->lineEditData->text().toInt(&ok,16); //以十六进制数读入QString str3 = QString("%1").arg(val3, 2, 16, QLatin1Char('0'));uint8_t len = 6 + static_cast<uint8_t>(str3.length()/2);QString hexStr = QString("%1").arg(len, 2, 16, QLatin1Char('0'));str.append(hexStr);str.append(str1);str.append(str2);str.append(str3);uint8_t crc;QString tmp;for(int i=0; i<str3.length(); i+=2){tmp = ui->lineEditData->text()[i];tmp += ui->lineEditData->text()[i+1];crc+= tmp.toInt(&ok,16);tmp = "";}crc += len;crc += val1;crc += val2;QString hexCrc= QString("%1").arg(crc, 2, 16, QLatin1Char('0'));str.append(hexCrc);str.append("5A");str = str.toUpper();ui->lineEdit_protocalData->insert(str);
}void Widget::sendProtocalHexData()
{QString message = ui->lineEdit_protocalData->text();serialPort->write(QByteArray::fromHex(message.toLatin1()));
}
ui
代码框架概览
演示视频
串口上位机(基本设置/协议收发)演示_哔哩哔哩_bilibili
串口上位机(基本设置/协议收发)演示
相关文章:
Qt串口基本设置与协议收发
前言 1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下 2.comboBox没有点击的信号,所以做了一个触发的功能 3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊 4.给自己挖个坑,下一期做一个查看波形的上位…...
interview3-微服务与MQ
一、SpringCloud篇 (1)服务注册 常见的注册中心:eureka、nacos、zookeeper eureka做服务注册中心: 服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、…...
kafka详解一
kafka详解一 1、消息引擎背景 根据维基百科的定义,消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的异步式数据传递. 即:系统 A 发送消息给消息引擎系统,系统 B 从消息引擎系统中读取 A…...
Flutter yuv 转 rgb
1、引用yuv_converter库 yuv_converter: ^0.0.1 2、导入头文件: import package:yuv_converter/yuv_converter.dart;3、yuv转rgb YuvConverter.yuv420NV21ToRgba8888(yuvRawData, 512, 512) 根据yuv格式选择不同的api。 举个例子: void initState() …...
MySQL——子查询
2023.9.8 相关学习笔记: #子查询 /* 含义: 出现在其他语句中的select语句,称为子查询或内查询 外部的查询语句,称为主查询或外查询分类: 按子查询出现的位置:select后面:仅仅支持标量子查询fro…...
Java学习笔记---多态
面向对象三大特征之一(继承,封装,多态) 多态的应用场景:根据传递对象的不同,调用不同的show方法 一、多态的定义 同类型的对象,表现出的不同形态(对象的多种形态) 二…...
2023-09-10 LeetCode每日一题(课程表 II)
2023-09-10每日一题 一、题目编号 210. 课程表 II二、题目链接 点击跳转到题目位置 三、题目描述 现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] [ai, bi] ,表示在…...
合并区间【贪心算法】
合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 class Solution {public int[][] merge(int[…...
2023,软件测试人的未来在哪里?
2023年,IT行业出现空前的萧条,首先是年初一开始各大厂像着了魔似的不约而同的纷纷裁员、降薪、奖金包缩水,随之而来的是需求萎缩,HC减少或封锁等等。 而有幸未被列入裁员名单的在职人员,庆幸之余也心有余悸࿰…...
Python中的Numpy向量计算(R与Python系列第三篇)
目录 一、什么是Numpy? 二、如何导入NumPy? 三、生成NumPy数组 3.1利用序列生成 3.2使用特定函数生成NumPy数组 (1)使用np.arange() (2)使用np.linspace() 四、NumPy数组的其他常用函数 (1)np.z…...
LeetCode刷题笔记【27】:贪心算法专题-5(无重叠区间、划分字母区间、合并区间)
文章目录 前置知识435. 无重叠区间题目描述参考<452. 用最少数量的箭引爆气球>, 间接求解直接求"重叠区间数量" 763.划分字母区间题目描述贪心 - 建立"最后一个当前字母"数组优化marker创建的过程 56. 合并区间题目描述解题思路代码① 如果有重合就合…...
nvidia-smi 命令详解
nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章: nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序,用于监控和管理 NVIDIA G…...
fork()函数的返回值
在程序中,int pd fork() 是一个典型的 fork() 调用。fork() 函数会创建一个新的进程,然后在父进程中返回子进程的进程ID(PID),在子进程中返回0。所以 pd 的值会根据当前进程是父进程还是子进程而有所不同:…...
Stable Diffusion WebUI挂VPN不能跑图解决办法(Windows)
如何解决SD在打开VPN的状态不能运行的问题 在我们开VPN的时候会出现无法生成图片,也无法做其他任何事,这个时候是不是很着急呢? 别急,我这里会说明如何解决。 就像这样,运行半天生成不了图,有时还会出现…...
Android的本地数据
何为本地,即写完之后除非手动修改,否像嘎了一样在那固定死了 有些需求可能也会要求我们去写死数据,因为这需求是一成不变的,那么你通常会用什么方法写死呢? 1. 本地存储-SharedPreferences 此方法可以长时间保存于手…...
android NDK 开发包,网盘下载,不限速
记录下ndk 开发包的地址,分享给大家。 另外有Android studio的下载包, 在另一篇文章 链接:http://t.csdn.cn/JSr9x Android Studio.exe 下载 2023 最新更新,网盘下载_hsj-obj的博客-CSDN博客 主要是19-25,其他的没有…...
【每日一题Day320】LC2651计算列车到站时间 | 数学
计算列车到站时间【LC2651】](https://leetcode.cn/problems/calculate-delayed-arrival-time/) 给你一个正整数 arrivalTime 表示列车正点到站的时间(单位:小时),另给你一个正整数 delayedTime 表示列车延误的小时数。 返回列车实…...
C语言柔性数组详解:让你的程序更灵活
柔性数组 一、前言二、柔性数组的用法三、柔性数组的内存分布四、柔性数组的优势五、总结 一、前言 仔细观察下面的代码,有没有看出哪里不对劲? struct S {int i;double d;char c;int arr[]; };还有另外一种写法: struct S {int i;double …...
Redis-带你深入学习数据类型list
目录 1、list列表 2、list相关命令 2.1、添加相关命令:rpush、lpush、linsert 2.2、查找相关命令:lrange、lindex、llen 2.3、删除相关命令:lpop、rpop、lrem、ltrim 2.4、修改相关命令:lset 2.5、阻塞相关命令:…...
react拖拽依赖库react-dnd
注:对于表格自定义行可以拖拽和树自定义节点可以拖拽等比较适用,其余的拖拽处理可以使用dragstart,drop等js原生事件来实现 react-dnd使用方法很简单,直接上干货 第一步安装依赖并引入 import { DndProvider } from react-dnd;…...
win10环境安装使用docker-maxwell
目的:maxwell可以监控mysql数据变化,并同步到kafka、mq或tcp等。 maxwell和canal区别: maxwell更轻量,canal把表结构也输出了 docker bootstrap可导出历史数据,canal不能 环境 :win10,mysql5…...
Docker部署RabbitMQ
Docker部署RabbitMQ 介绍 RabbitMQ是一个开源的消息队列系统,它被设计用于在应用程序之间传递消息。它采用了AMQP(高级消息队列协议)作为底层通信协议,这使得它能够在不同的应用程序之间进行可靠的消息传递。 那么,…...
23个react常见问题
1、setState 是异步还是同步? 合成事件中是异步 钩子函数中的是异步 原生事件中是同步 setTimeout中是同步 相关链接:你真的理解setState吗?: 2、聊聊 react16.4 的生命周期 图片 相关连接:React 生命周期 我对 Reac…...
【python基础】——Anaconda下包更新的坑及安装与卸载、及安装后Jupyter Notebook没反应的解决方法
文章目录 前言一、起因:如何一步步走到卸载重装anaconda?二、卸载anaconda二、重新安装anaconda三、关于安装Anaconda后,打开Jupyter Notebook运行代码没反应且in[ ]没有*前言 本文主要用来记录自己近期踩坑的一些复盘。其中坑有: ‘.supxlabel’ 不起作用的解决pip list 与…...
CSS 中的 display 和 visibility
CSS 中的 display 和 visibility 都可以设置一个元素在浏览器中的显示或隐藏效果。 display: 隐藏某个元素时,不会占用任何空间。换句话讲,不会影响布局。visibility: 隐藏某个元素时,仍需占用与未隐藏之前一样的空间。换句话讲,…...
解决mysql报错this is incompatible with DISTINCT
环境 centos 9 php7.4 mysql5.7 问题 mysql查询报如下错误: SQLSTATE[HY000]: General error: 3065 Expression #1 of ORDER BY clause is not in SELECT list, references column hst_csc.q.timestamp which is not in SELECT list; this is incompatible with…...
C++-map和set
本期我们来学习map和set 目录 关联式容器 键值对 pair 树形结构的关联式容器 set multiset map multimap 关联式容器 我们已经接触过 STL 中的部分容器,比如: vector 、 list 、 deque 、forward_list(C11)等,这些容器统称为序列式…...
微信小程序AI类目-深度合成-AI问答/AI绘画 互联网信息服务算法备案审核通过教程
近期小程序审核规则变化后,很多使用人类小徐提供的chatGPT系统的会员上传小程序无法通过审核,一直提示需要增加深度合成-AI问答、深度合成-AI绘画类目,该类目需要提供互联网信息服务算法备案并上传资质,一般对企业来说这种务很难实…...
蓝桥杯官网练习题(星期一)
题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 整个 2020 世纪(1901 年 1 月 1 日至 2000 年 12 月 3131 日之间),一共有多少个星期一?(不要告诉我你不知道今天是星…...
centos7更新podman
实验环境:centos7.7.1908 1.安装podman并查看版本 yum install podman podman -v 当前podman版本信息是1.6.4 2.更新podman版本 通过查看资料显示centos 7 支持最高版本为 3.4.4,更新podman大致有以下四步: golang 安装(本次使用版本: 1.…...
长沙做网站的/百度引流推广哪家好
运行程序报错:TypeError: Model hr.employee does not exist in registry. 产生问题现象:新建了一个模块,去继承修改原模块的内容,程序代码格式检查无问题,但是报错 Traceback (most recent call last): File "…...
自己如何建设网站/宁波正规优化seo价格
C编译器生成的默认函数话题引入: 对象的赋值与复制是如何进行的?他们的区别是什么?如果一个空的自定义类型能否执行这些操作? 对象赋值:通过“ ”运算符重载 User a(10),b; b a; 对象复制:调用拷贝构造函…...
网址关键词查询网站/网络营销的特征和功能
1.开篇介绍 在开始看本篇文章之前先允许我打断一下各位的兴致。其实这篇文章本来是没有打算加“开篇介绍”这一小节的,后来想想还是有必要反馈一下读者的意见。经过前三篇文章的详细讲解,我们基本上对LINQ框架的构成原理有了一个根本的认识,…...
投注网站建设需要多少钱/免费搭建网站的软件
03月23日,钛博士机器人侦测到 25 起发生在科技和互联网行业的投融资或并购事件,其中 11 起发生在中国境内,14 起发生在海外,总计交易额超过118.26亿人民币。中国境内科技行业投融资总额约5.52亿人民币,单笔最大交易事件…...
中英文网站用同域名/百度知道灰色词代发收录
文章目录加载远程的数据集散点图与乱码问题博文配套视频课程:24小时实现从零到AI人工智能 加载远程的数据集 Matplotlib 是 Python 的绘图库。 它可与 NumPy 一起使用,提供了一种有效的 MatLab 开源替代方案。 默认情况下Aancanda已经集成了MatplotLib可…...
免费网站建设公司/上海关键词推广
“Shift空格” 是全角和半角的切换...