FPGA|CPLD|ASICwilliam hill官网
直播中

陈彭鑫

9年用户 165经验值
擅长:嵌入式技术
私信 关注
[经验]

【DIGILENT挑战赛】WiFi电子钢琴

``
摘要:针对影音娱乐的挑战主题,设计了一款基于ArtyWiFi电子钢琴。利用普通电脑键盘的104个按键代替钢琴的88个音符键,不同的按键对应不同的音符。FPGA驱动键盘与WiFi模块,Qt建立服务器并控制音响播放音符文件。FPGA通过WiFi模块与远端服务器和音响建立连接,用户可以感受无线钢琴的体验。

关键词:ArtyWiFi;电子钢琴;服务器;

首先看一下演示视频:
[media]http://v.youku.com/v_show/id_XMjczMzk2OTg2OA==.html?spm=a2h3j.8428770.3416059.1[/media]

另外,本设计报告的技术要点包括:1Basys3驱动键盘获取键值并发送;2Arty-Board接收键值并控制WiFi模块;3QT编写服务器并根据键值播放音符。由于本挑战赛主要针对Arty开发板,所以详细阐述“2Arty-Board接收键值并控制WiFi模块”,其余两个技术要点不做重点,只作介绍并共享源码。

0 引言
你是否爱音乐,是否有个钢琴梦,但又因动辄上万元的价格止住思绪。没关系,让我们来DIY一个电子钢琴,还是无线的哦!本设计通过电脑键盘,代替钢琴键盘,实现88个音符的无线演奏。
在进行本设计之前,我们必须有一些ArtyBasys3的开发功底。好在,Digilent官网已经为我们提供了基础开发例程。所以,如果你是一个新手,那么一定要先看下面的例程。
Arty-Board例程(教程链接):

Basys3例程(教程链接):
此外,Digilent将项目源码都上传到了Github上(链接:https://github.com/Digilent。很多资源都是直接可以获取的,无需自己造轮子,站在巨人的肩膀上,才能看得更远。所以,你还需要学会这两件事:
学会善用DigilentXilinx的开放资源,可以加快我们的开发速度。

1 硬件设计1.1 系统框图

QQ截图20170428145445.jpg
1 系统框图
如图1所示,首先将键盘通过USB接口连接Basys3(因为Arty没有USB Host的接口啊,所以借助了Basys3),Basys3驱动键盘、读取键值,并利用串口通信将键值传递给ArtyArty拿到键值之后,再通过WiFi模块传输到远端服务器。服务器使用的是家用计算机,利用Qt编写了TCP通信的程序,使之与Arty端的WiFi模块建立连接。上位机方面,制作了88个音符wav文件,利用QSound类根据键值播放对应的音符WAV文件。用户端敲击键盘,计算机端播放音符,从而实现了WiFi电子钢琴的功能。
1.2 硬件原料
如图2所示,从左至右依次为:音响、ArtyWiFi模块,Basys3、键盘。

2 原料
1.3 WiFi模块原理图及PCB(附件)
WiFi模块采用的是ESP8266,一直蛮受欢迎的一款WiFi芯片。芯片原厂是上海乐鑫,经深圳安信可封装成模块后二次出售。可以通过AT命令或SDK开发。蛮简单的一个板子,原理图和PCB如图3和图4,大家见笑了。
图片4.png
3 WiFi模块原理图
图片5.png
4 WiFi模块PCB
2 软件设计2.1 Basys3驱动Key-Board
将键盘连接Basys3USB HID,在USB HID的下方有个微控制器PIC24FJ128GB106 ,在为PIC24FJ128GB106 的内部固化了USBPS2信号的固件,所以连接FPGA芯片的并不是USB信号,而是PS2信号。
程序代码以共享,见附件。
图片6.png
5 代码结构图
图片7.png
6 顶层文件
5为代码的结构图,主要分为两个部分:1PS2信号的解析;2)串口发送。我们打开顶层文件top.v,如图6,可以看到共有3个输入信号,1个输出信号。输入信号分别为clk(控制时钟),PS2DataPS2Clk,输出信号为txFPGA在程序中,通过PS2DataPS2Clk两个信号解析出键值,和“按下”“松开”的状态。将键值通过tx信号发送出去。所以Basys3Arty之间就是通过tx信号线相连。
具体代码无法仔细分析,大家看一下我共享出来的代码吧。
2.2 Arty通过MicroBlaze软核接收键值、控制WiFi
按步骤依次来吧:
1)打开Vivado,创建新工程
图片8.png
2)键入工程名和路径。注意不能有中文和空格!!然后,点击Next.
图片9.png
3)选择RTL Project,勾选Do not specify sources at this time
图片10.png
4)选择Boards,板卡列表中选择Arty,再点击Next。注意:如果你没有发现Arty这个选项,说明你没有安装Digilent板卡文件。因为,Vivado下载安装后,Boards文件只包含Xilinx的板卡。Arty属于第三方Digilent公司生产的开发板,所以不包含在Vivado中。 那么怎么办呢? 很简单,请仔细阅读本文的引言部分:2)如何安装Digilent板卡文件到Vivado安装路径
图片11.png
5)然后就可以看到项目预配置的信息,点击Finish即可。
6)创建新的Block Design,在左侧导航栏点击Create Block Design,在弹出的菜单中输出设计名称为system(可随意).
图片13.png
创建完成。
图片14.png
7)在Block Design右下角点击Board,然后就可以看到很多为Arty定制的IP核,这些IP核囊括了Arty板子上所有的外设资源。
图片15.png
8)首先拖拽System Clock到空白区域,Vivado会自动连接
图片16.png
9)双击时钟IP,配置输出clk_out1166.667 MHzclk_out12200 MHz。选择Reset TypeActive Low,即低电平有效。
图片17.png
10)拖拽出DDR3 SDRAM
图片18.png
11)删除clk_ref_i和sys_clk_i。
图片19.png
12)做下图所示的连接
图片20.png
13)点击上方的Run Connection Automation。注意:自动连接之后会自动产生resetn的输入引脚,要将其连接Clocking Wizard核的resetn输入脚。务必连接,否则系统不工作。
图片21.png
14)点击Add IP添加MicroBlaze
图片22.png
图片23.png
15)点击Run Block Automation,打开MicroBlaze控制菜单。Local Memory设为32KBCache Configuration设为16KB。还有务必选择Clock Connection/mig_7series_0/ui_clk,点击OK
图片24.png
16)Board中选择USB UART,添加到Block Design
图片25.png
17)点击Run Connection Automation,勾选All Automation复选框,点击OK。点击Regenerate Layout可以调整视图,或者在空白处点击向左拖拽。
图片26.png
18)点击Validate Design来检查是否有连接错误
19)确认无误后,回到Source界面。
图片27.png
20)右击Block Design的文件名system,在快捷菜单中点击Create HDL Wrapper,切记勾选Let Vivado manage wrapper and auto-update,并点击OK
图片28.png
21)由于默认的USB UART连接Arty BoardUSB,但我们希望用这个UART连接WiFi,所以就需要重新建立一个XDC的管脚约束文件。
快捷键ALT+A,弹出菜单后选择Add or create constraints,点击next
图片29.png
点击Create File,然后在弹出的菜单中输入文件名,点击OK即可。
图片30.png
编辑新建的XDC管脚约束文件,添加以下内容即可。
set_property PACKAGE_PIN F3 [get_ports u***_uart_rxd]
set_property PACKAGE_PIN D13 [get_ports u***_uart_txd]
set_property IOSTANDARD LVCMOS33 [get_ports u***_uart_txd]
set_property IOSTANDARD LVCMOS33 [get_ports u***_uart_rxd]
22)点击Generate Bitstream,这会耗费一定的时间。
23)在主工具栏的File菜单下选择 Export→Export Hardware。勾选Include Bitstream,并点击OK。这会将我们的Block Design作为BSP导出到Xilinx SDK中。
图片31.png
23)File菜单下点击Launch SDK
图片32.png
24)在新打开的Xilinx SDK中可以看到这样的界面
图片33.png
25)创建一个新的 Xilinx→Application Project,输入项目名称,选择硬件平台,点击Next。在下一级菜单中选择Empty Application,点击Finish即可。        
图片34.png
图片35.png
26)接下来就是C程序编程了,东西比较多,无法细讲,比如下面三个经常用到的头文件。因为我用的是串口WIFI模块,所以SDKC程序主要就是串口的接收、发送和中断。我把源码分享出,大家感兴趣的可以下载下来看看。
#include "xparameters.h"
#include "xuartlite.h"
#include "microblaze_sleep.h"

2.3 服务器端Qt编程
上位机端的程序主要包括两点:
1)服务器的搭建:
由于本设计,即电子钢琴是通过WIFI来进行键值无线传输的。为保证减值传输的可靠性,这里采用TCP/IP通信的手段。Arty板卡连接的WIFI模块作为客户端,计算机的网卡作为服务器端。
2)音符的播放:
网上可以下载到钢琴88键的WAV音频文件,这里通过WIFI传输的减值不同,播放不同的音频文件。
因为这次是FPGA的挑战赛,所以我也不好意思在这里臭显摆QT的编程知识。懂的人都知道并不难,使用QTQTcpserverQTcpclientQsoundQDir类便可以实现上述的功能。源码同样共享,感兴趣的人可以下载下来看看。

开发环境:Qtcreator5.7.01. 打开Qtcreator,选择Application -> Qt Widgets Application,点击选择。
图片36.png
2. 设置项目名称为MyTcpServer,设置系项目路径,点击下一步
图片37.png
3. 然后选择编译工具
软件预装了Qt5.7.0 MSVC2013-64bit版本的qt kit。值得一提的是,我这里添加了一个Qt5.7-Static,这是我自己静态编译的一个qt kit。那么二者有什么不同呢?是这样的,用前者编译链接生成的exe可执行文件,只能在本机运行(或者说只能在具备Qt5.7.0的依赖库的计算机上运行);而通过后者编译链接生成的exe可执行文件,可以在任何Windows操作系统中运行,无需依赖库。这是因为后者将项目所需的依赖库编译到了exe本体中。 Qt的强大还不止于此,它生成的exe可执行文件可以运行在windowslinuxandroid等系统的硬件平台上,只要你设置了对应版本的编译器。

图片38.png
4. 程序源码已经在共享在本文末尾的附件中,我们来看一下程序中有哪些文件吧。
如下图,需要用到的文件其实并不多,因为Qt已经帮助开发者“造好了很多轮子”,用户只需要负责“驾驶”就行了。不多说,直接看代码,开车了。
图片39.png

---MyTcpSercer.pro
该文件是项目的配置文件,里面包含了系统的很多make信息,如下图。Core guiqt最基本的界面组件。Network提供了网络支持,因为我们用到了QTcpServerQTcpSocket类来进行WiFi无线通信,所以必须添加Qt += networkMultimedia提供了多媒体影音的支持,因为我们用到了QSound类来播放音符文件,所以必须添加Qt += multimedia
  1. #-------------------------------------------------
  2. #
  3. # Project created by QtCreator 2017-03-20T18:54:40
  4. #
  5. #-------------------------------------------------

  6. QT       += core gui
  7. QT       += network
  8. QT       += multimedia
  9. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

  10. TARGET = MyTcpServer
  11. TEMPLATE = app


  12. SOURCES += main.cpp
  13.         mytcpserver.cpp

  14. HEADERS  += mytcpserver.h

  15. FORMS    += mytcpserver.ui

---mytcpserver.ui
该文件是项目的界面文件,所以用ui后缀来命名。如下图,界面包括3部分,首先是接收窗口:这里将Arty传输过来的“键值-音符”信息显示在空白处。其次是发送窗口,在TextEditor中输入需要发送的数值,点击“发送”按钮便可以将消息发送给Arty。最后是网络设置串口,也是最重要的一个环节。这里的本机IP地址可以在程序运行时自动获取,本地端口号可以随意设置,点击监听之后,便可以接收Arty的连接请求。
图片40.png

---mytcpserver.h
该文件是Qt主类的头文件,里面包含了Tcp/Ip的用户自定义函数原型声明。以及界面操作的“信号-槽函数”。多说一句,“信号与槽”的机制真的是很方便,优雅而实用。
  1. #ifndef MYTCPSERVER_H
  2. #define MYTCPSERVER_H

  3. #include <QMainWindow>
  4. #include <QTcpServer>
  5. #include <QTcpSocket>
  6. #include <QNetworkInterface>
  7. #include <QMessageBox>
  8. #include <QDir>
  9. #include <QSound>
  10. #include <QDebug>
  11. namespace Ui {
  12. class MyTcpServer;
  13. }

  14. class MyTcpServer : public QMainWindow
  15. {
  16.     Q_OBJECT

  17. public:
  18.     explicit MyTcpServer(QWidget *parent = 0);
  19.     ~MyTcpServer();

  20. private:
  21.     Ui::MyTcpServer *ui;
  22.     QTcpServer *tcpServer;
  23.     QList<QTcpSocket*> tcpClient;
  24.     QTcpSocket *currentClient;
  25.     QDir *dir;

  26. private slots:
  27.     void NewConnectionSlot();
  28.     void disconnectedSlot();
  29.     void ReadData();

  30.     void on_btnConnect_clicked();
  31.     void on_btnSend_clicked();
  32.     void on_btnClear_clicked();
  33. };

  34. #endif // MYTCPSERVER_H

---mytcpserver.cpp
该源文件中包含了mytcpserver.h中所有函数的具体实现。
  1. #include "mytcpserver.h"
  2. #include "ui_mytcpserver.h"

  3. MyTcpServer::MyTcpServer(QWidget *parent) :
  4.     QMainWindow(parent),
  5.     ui(new Ui::MyTcpServer)
  6. {
  7.     ui->setupUi(this);

  8.     tcpServer = new QTcpServer(this);
  9.     ui->edtIP->setText(QNetworkInterface().allAddresses().at(1).toString());   //获取本地IP
  10.     ui->btnConnect->setEnabled(true);
  11.     ui->btnSend->setEnabled(false);

  12.     connect(tcpServer, SIGNAL(newConnection()), this, SLOT(NewConnectionSlot()));
  13. }

  14. MyTcpServer::~MyTcpServer()
  15. {
  16.     delete ui;
  17. }
  18. // newConnection -> newConnectionSlot 新连接建立的槽函数
  19. void MyTcpServer::NewConnectionSlot()
  20. {
  21.     currentClient = tcpServer->nextPendingConnection();
  22.     tcpClient.append(currentClient);
  23.     ui->cbxConnection->addItem(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])
  24.                                           .arg(currentClient->peerPort()));
  25.     connect(currentClient, SIGNAL(readyRead()), this, SLOT(ReadData()));
  26.     connect(currentClient, SIGNAL(disconnected()), this, SLOT(disconnectedSlot()));
  27. }

  28. // 客户端数据可读信号,对应的读数据槽函数
  29. void MyTcpServer::ReadData()
  30. {
  31.     // 由于readyRead信号并未提供SocketDecriptor,所以需要遍历所有客户端
  32.     for(int i=0; i<tcpClient.length(); i++)
  33.     {
  34.         QByteArray buffer = tcpClient[i]->readAll();
  35.         if(buffer.isEmpty())    continue;

  36.         static QString IP_Port, IP_Port_Pre;
  37.         IP_Port = tr("[%1:%2]:").arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])
  38.                                      .arg(tcpClient[i]->peerPort());

  39.         // 若此次消息的地址与上次不同,则需显示此次消息的客户端地址
  40.         if(IP_Port != IP_Port_Pre)
  41.             ui->edtRecv->append(IP_Port);

  42.         ui->edtRecv->append(buffer);

  43.         //更新ip_port
  44.         IP_Port_Pre = IP_Port;


  45.         //播放音符
  46.         for(int i=0; i<buffer.length(); i++)
  47.         {
  48.             qDebug() << buffer.at(i);
  49.             switch(buffer.at(i))
  50.             {
  51.             //主键盘区 第一行
  52.             case 0x16:QSound::play(dir->currentPath().append("WavPiano52-C.wav"));break;
  53.             case 0x1E:QSound::play(dir->currentPath().append("WavPiano54-D.wav"));break;
  54.             case 0x26:QSound::play(dir->currentPath().append("WavPiano56-E.wav"));break;
  55.             case 0x25:QSound::play(dir->currentPath().append("WavPiano57-F.wav"));break;
  56.             case 0x2E:QSound::play(dir->currentPath().append("WavPiano59-G.wav"));break;
  57.             case 0x36:QSound::play(dir->currentPath().append("WavPiano61-A.wav"));break;
  58.             case 0x3D:QSound::play(dir->currentPath().append("WavPiano63-B.wav"));break;
  59.             case 0x3E:QSound::play(dir->currentPath().append("WavPiano64-C.wav"));break;
  60.             case 0x46:QSound::play(dir->currentPath().append("WavPiano66-D.wav"));break;
  61.             case 0x45:QSound::play(dir->currentPath().append("WavPiano68-E.wav"));break;
  62.             case 0x4E:QSound::play(dir->currentPath().append("WavPiano69-F.wav"));break;
  63.             case 0x55:QSound::play(dir->currentPath().append("WavPiano71-G.wav"));break;
  64.             //主键盘区 第二行
  65.             case 0x15:QSound::play(dir->currentPath().append("WavPiano40-C.wav"));break;
  66.             case 0x1D:QSound::play(dir->currentPath().append("WavPiano42-D.wav"));break;
  67.             case 0x24:QSound::play(dir->currentPath().append("WavPiano44-E.wav"));break;
  68.             case 0x2D:QSound::play(dir->currentPath().append("WavPiano45-F.wav"));break;
  69.             case 0x2C:QSound::play(dir->currentPath().append("WavPiano47-G.wav"));break;
  70.             case 0x35:QSound::play(dir->currentPath().append("WavPiano49-A.wav"));break;
  71.             case 0x3C:QSound::play(dir->currentPath().append("WavPiano51-B.wav"));break;
  72.             case 0x43:QSound::play(dir->currentPath().append("WavPiano52-C.wav"));break;
  73.             case 0x44:QSound::play(dir->currentPath().append("WavPiano54-D.wav"));break;
  74.             case 0x4D:QSound::play(dir->currentPath().append("WavPiano56-E.wav"));break;
  75.             case 0x54:QSound::play(dir->currentPath().append("WavPiano57-F.wav"));break;
  76.             case 0x5B:QSound::play(dir->currentPath().append("WavPiano59-G.wav"));break;
  77.             case 0x5D:QSound::play(dir->currentPath().append("WavPiano61-A.wav"));break;
  78.             //主键盘区 第三行
  79.             case 0x1C:QSound::play(dir->currentPath().append("WavPiano28-C.wav"));break;
  80.             case 0x1B:QSound::play(dir->currentPath().append("WavPiano30-D.wav"));break;
  81.             case 0x23:QSound::play(dir->currentPath().append("WavPiano32-E.wav"));break;
  82.             case 0x2B:QSound::play(dir->currentPath().append("WavPiano33-F.wav"));break;
  83.             case 0x34:QSound::play(dir->currentPath().append("WavPiano35-G.wav"));break;
  84.             case 0x33:QSound::play(dir->currentPath().append("WavPiano37-A.wav"));break;
  85.             case 0x3B:QSound::play(dir->currentPath().append("WavPiano39-B.wav"));break;
  86.             case 0x42:QSound::play(dir->currentPath().append("WavPiano40-C.wav"));break;
  87.             case 0x4B:QSound::play(dir->currentPath().append("WavPiano42-D.wav"));break;
  88.             case 0x4C:QSound::play(dir->currentPath().append("WavPiano44-E.wav"));break;
  89.             case 0x52:QSound::play(dir->currentPath().append("WavPiano45-F.wav"));break;
  90.             //主键盘区 第四行
  91.             case 0x1A:QSound::play(dir->currentPath().append("WavPiano16-C.wav"));break;
  92.             case 0x22:QSound::play(dir->currentPath().append("WavPiano18-D.wav"));break;
  93.             case 0x21:QSound::play(dir->currentPath().append("WavPiano20-E.wav"));break;
  94.             case 0x2A:QSound::play(dir->currentPath().append("WavPiano21-F.wav"));break;
  95.             case 0x32:QSound::play(dir->currentPath().append("WavPiano23-G.wav"));break;
  96.             case 0x31:QSound::play(dir->currentPath().append("WavPiano25-A.wav"));break;
  97.             case 0x3A:QSound::play(dir->currentPath().append("WavPiano27-B.wav"));break;
  98.             case 0x41:QSound::play(dir->currentPath().append("WavPiano28-C.wav"));break;
  99.             case 0x49:QSound::play(dir->currentPath().append("WavPiano30-D.wav"));break;
  100.             case 0x4A:QSound::play(dir->currentPath().append("WavPiano32-E.wav"));break;
  101.             //主键盘区 小键盘
  102.             case 0x69:QSound::play(dir->currentPath().append("WavPiano40-C.wav"));break;
  103.             case 0x72:QSound::play(dir->currentPath().append("WavPiano42-D.wav"));break;
  104.             case 0x7A:QSound::play(dir->currentPath().append("WavPiano44-E.wav"));break;
  105.             case 0x6B:QSound::play(dir->currentPath().append("WavPiano45-F.wav"));break;
  106.             case 0x73:QSound::play(dir->currentPath().append("WavPiano47-G.wav"));break;
  107.             case 0x74:QSound::play(dir->currentPath().append("WavPiano49-A.wav"));break;
  108.             case 0x6C:QSound::play(dir->currentPath().append("WavPiano51-B.wav"));break;
  109.             case 0x75:QSound::play(dir->currentPath().append("WavPiano52-C.wav"));break;
  110.             case 0x7D:QSound::play(dir->currentPath().append("WavPiano54-D.wav"));break;
  111.             case 0x79:QSound::play(dir->currentPath().append("WavPiano56-E.wav"));break;
  112.             case 0x77:QSound::play(dir->currentPath().append("WavPiano57-F.wav"));break;
  113.             //case 0x4A:QSound::play(dir->currentPath().append("WavPiano59-G.wav"));break;
  114.             case 0x7C:QSound::play(dir->currentPath().append("WavPiano61-A.wav"));break;
  115.             case 0x7B:QSound::play(dir->currentPath().append("WavPiano63-B.wav"));break;
  116.             case 0x70:QSound::play(dir->currentPath().append("WavPiano35-G.wav"));break;
  117.             case 0x71:QSound::play(dir->currentPath().append("WavPiano37-A.wav"));break;
  118.             case 0x5A:QSound::play(dir->currentPath().append("WavPiano39-B.wav"));break;
  119.             default:break;
  120.             }
  121.         }

  122.     }
  123. }
  124. // disconnected -> disconnectedSlot 客户端断开连接的槽函数
  125. void MyTcpServer::disconnectedSlot()
  126. {
  127.     //由于disconnected信号并未提供SocketDescriptor,所以需要遍历寻找
  128.     for(int i=0; i<tcpClient.length(); i++)
  129.     {
  130.         if(tcpClient[i]->state() == QAbstractSocket::UnconnectedState)
  131.         {
  132.             // 删除存储在combox中的客户端信息
  133.             ui->cbxConnection->removeItem(ui->cbxConnection->findText(tr("%1:%2")
  134.                                   .arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])
  135.                                   .arg(tcpClient[i]->peerPort())));
  136.             // 删除存储在tcpClient列表中的客户端信息
  137.              tcpClient[i]->destroyed();
  138.              tcpClient.removeAt(i);
  139.         }
  140.     }
  141. }
  142. // 监听--断开
  143. void MyTcpServer::on_btnConnect_clicked()
  144. {
  145.     if(ui->btnConnect->text()=="监听")
  146.     {
  147.         bool ok = tcpServer->listen(QHostAddress::Any, ui->edtPort->text().toInt());
  148.         if(ok)
  149.         {
  150.             ui->btnConnect->setText("断开");
  151.             ui->btnSend->setEnabled(true);
  152.         }
  153.     }
  154.     else
  155.     {
  156.         for(int i=0; i<tcpClient.length(); i++)//断开所有连接
  157.         {
  158.             tcpClient[i]->disconnectFromHost();
  159.             bool ok = tcpClient[i]->waitForDisconnected(1000);
  160.             if(!ok)
  161.             {
  162.                 // 处理异常
  163.             }
  164.             tcpClient.removeAt(i);  //从保存的客户端列表中取去除
  165.         }
  166.         tcpServer->close();     //不再监听端口
  167.         ui->btnConnect->setText("监听");
  168.         ui->btnSend->setEnabled(false);
  169.     }
  170. }
  171. // 发送数据
  172. void MyTcpServer::on_btnSend_clicked()
  173. {
  174.     QString data = ui->edtSend->toPlainText();
  175.     if(data == "")  return;    // 文本输入框为空时
  176.     //全部连接
  177.     if(ui->cbxConnection->currentIndex() == 0)
  178.     {
  179.         for(int i=0; i<tcpClient.length(); i++)
  180.             tcpClient[i]->write(data.toLatin1()); //qt5除去了.toAscii()
  181.     }
  182.     //指定连接
  183.     else
  184.     {
  185.         QString clientIP = ui->cbxConnection->currentText().split(":")[0];
  186.         int clientPort = ui->cbxConnection->currentText().split(":")[1].toInt();
  187. //        qDebug() << clientIP;
  188. //        qDebug() << clientPort;
  189.         for(int i=0; i<tcpClient.length(); i++)
  190.         {
  191.             if(tcpClient[i]->peerAddress().toString().split("::ffff:")[1]==clientIP
  192.                             && tcpClient[i]->peerPort()==clientPort)
  193.             {
  194.                 tcpClient[i]->write(data.toLatin1());
  195.                 return; //ip:port唯一,无需继续检索
  196.             }
  197.         }
  198.     }
  199. }

  200. void MyTcpServer::on_btnClear_clicked()
  201. {
  202.     ui->edtRecv->clear();
  203. }

---main.cpp
最后是main函数文件,main函数是整个项目程序的入口函数,其作用就是实例化一个MyTcpServer类,显示界面。
  1. #include "mytcpserver.h"
  2. #include <QApplication>

  3. int main(int argc, char *argv[])
  4. {
  5.     QApplication a(argc, argv);
  6.     MyTcpServer w;
  7.     w.show();

  8.     return a.exec();
  9. }


至此,挑战赛完成!
感谢发烧友william hill官网 的支持!感谢各位读者的时间!

本文相关附件如下:
WIFI原理图与PCB.rar (776.06 KB)
(下载次数: 9, 2017-4-28 15:00 上传)
88音符WAV文件.rar (6.82 MB)
(下载次数: 10, 2017-4-28 15:01 上传)
Arty.srcs.rar (1.27 MB)
(下载次数: 22, 2017-4-28 15:15 上传)
ipshared.rar (18.82 MB)
(下载次数: 16, 2017-4-28 15:16 上传)
Arty_others.rar (2.14 MB)
(下载次数: 15, 2017-4-28 15:18 上传)
Basys3程序.rar (641.6 KB)
(下载次数: 25, 2017-4-28 15:18 上传)
qt服务器端程序.rar (6.84 KB)
(下载次数: 8, 2017-4-28 15:18 上传)




`` 图片3.png 图片12.png

回帖(3)

真相只有我知道

2017-4-28 14:33:43
链接?什么链接
举报

陈彭鑫

2017-4-28 14:49:15

上传超链接失败。。。  然后直接把连接地址放在1) 2)这些文字的后面了
举报

lee_st

2017-4-29 11:45:10
进来学习一下下了,
举报

更多回帖

发帖
×
20
完善资料,
赚取积分