0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于W5500的Modbus TCP服務(wù)器設(shè)計

CHANBAEK ? 來源:木南創(chuàng)智 ? 作者:尹家軍 ? 2022-12-14 16:03 ? 次閱讀

?前面我們設(shè)計實(shí)現(xiàn)了W5500的驅(qū)動程序,也講解了驅(qū)動的使用方式。在最近一次的項(xiàng)目應(yīng)用中,正好有一個使用W5500實(shí)現(xiàn)TCP通訊的需求,所以我們就使用該驅(qū)動程序輕松實(shí)現(xiàn)。這一篇中我們就來說一說基于我們W5500通訊驅(qū)動程序?qū)崿F(xiàn)TCP通訊的過程。

1、應(yīng)用需求

??在本次應(yīng)用中,要求實(shí)現(xiàn)一個基于W5500的Modbus TCP服務(wù)器。這個需求的描述雖然只有一句話,但是這個需求的內(nèi)容可不簡單。我們首先來分析一下這個需求的具體內(nèi)容。

??為了實(shí)現(xiàn)基于W5500的Modbus TCP服務(wù)器,我們必先須基于W5500實(shí)現(xiàn)一個TCP服務(wù)器。W5500本身是帶硬件協(xié)議棧的,但卻并不帶TCP服務(wù)器。不過在我們前面的關(guān)于外設(shè)驅(qū)動庫的系列文章中已經(jīng)封裝了W5500的驅(qū)動,其中就帶有一個TCP服務(wù)器,我們可以直接采用就可以了。

??其次我們要在TCP服務(wù)器的基礎(chǔ)上實(shí)現(xiàn)Modbus TCP協(xié)議。關(guān)于Modbus協(xié)議棧,我們以前的文章就講述過Modbus通訊協(xié)議棧的開發(fā)問題。而且我們已經(jīng)將我們開發(fā)的Modbus通訊協(xié)議棧開源。其中已經(jīng)封裝了Modbus TCP服務(wù)器對象,所以我們直接采用這一Modbus通訊協(xié)議棧就可以了。

??有了驅(qū)動和協(xié)議棧,我們還需要考慮應(yīng)用層面的具體問題,而且也只需要考慮應(yīng)用層面的具體問題。這里就看出我們前面封裝外設(shè)驅(qū)動和Modbus通訊協(xié)議棧的價值所在了。關(guān)于應(yīng)用層面的問題我們主要需要重點(diǎn)考慮幾個問題:

??第一,數(shù)據(jù)的存儲類型及地址范圍。我們知道Modbus協(xié)議常見的數(shù)據(jù)類型有4種。我們需要考慮在系統(tǒng)中需要使用到的類型及地址,這將決定Modbus協(xié)議數(shù)據(jù)處理回調(diào)函數(shù)的實(shí)現(xiàn)。

??第二,網(wǎng)絡(luò)配置問題,我們需要通過網(wǎng)絡(luò)訪問這臺下位機(jī)就需要要為其配置網(wǎng)絡(luò)。這存在靜態(tài)配置,動態(tài)配置和系統(tǒng)自動分配的問題。作為服務(wù)器,我們一般不會希望讓系統(tǒng)自動分配。所以我們需要考慮的是如何方便使用者為其分配地址的問題。

??第三,并發(fā)訪問的問題。掛載在網(wǎng)絡(luò)上的服務(wù)器肯定面臨多個客戶端來訪問的問題。W5500可以實(shí)現(xiàn)8個Socket,而Modbus TCP通用的默認(rèn)端口號是502,當(dāng)然也可以使用其它端口,只要不沖突就好。所以我們可以考慮使用不同的Socket和不同的端口號來實(shí)現(xiàn)并發(fā)訪問。

2、功能設(shè)計

??我們分析了基于W5500實(shí)現(xiàn)Modbus TCP服務(wù)器的需求。我們現(xiàn)在從硬件和軟件兩個方面來分析器功能的實(shí)現(xiàn)。

2.1、硬件功能設(shè)計

??我們知道W5500帶有硬件協(xié)議棧,集成有以太網(wǎng)控制器和物理層,所以對外我們只需要實(shí)現(xiàn)以太網(wǎng)變壓器和硬件接口就好了。但與控制器部分的連接則采用SPI接口,除此之外還需要提供中斷輸入和模式設(shè)定的相關(guān)接口。在這里我們設(shè)計器硬件連接如下:

??在上圖中,我們將中斷輸入引入到MCU的GPIO端口,而模式設(shè)定PMODE0、PMODE1、PMODE2均通過電阻上拉到電源。對于W5500來說PMODE0、PMODE1、PMODE2均為高電平表示開啟全部功能,所以我們直接拉高而不是引入到MCU引腳來控制。

2.2、軟件功能設(shè)計

??從需求來說,軟件的功能非常簡單,就是實(shí)現(xiàn)一個Modbus TCP服務(wù)器。但實(shí)際上,如我們前面所描述的那樣,軟件需要考慮的問題還是比較多的。從功能實(shí)現(xiàn)上主要有3個方面需要考慮:

??第一,實(shí)現(xiàn)TCP服務(wù)器,這個服務(wù)器用于在系統(tǒng)中輪詢處理,從W5500獲取數(shù)據(jù)和發(fā)送數(shù)據(jù)給W5500都需要通過這部分來實(shí)現(xiàn)。

??第二,TCP服務(wù)器得到數(shù)據(jù)后,我們需要解析數(shù)據(jù),并根據(jù)解析的上位數(shù)據(jù)決定進(jìn)一步的動作,還需要生成返回信息。這部分對應(yīng)功能就是Modbus TCP服務(wù)器的實(shí)現(xiàn)。

??第三,根據(jù)Modbus TCP服務(wù)器解析出的Modbus消息,需要決定下一步的動作,這個具體動作根據(jù)功能碼的不同可能有不同需求,所以我們需要根據(jù)具體的要求實(shí)現(xiàn)不同功能碼的動作。

??根據(jù)上述的設(shè)計,我們可以簡單的將需要實(shí)現(xiàn)的軟件功能圖示如下:

??上圖中,因?yàn)閃5500的TCP服務(wù)器以及Modbus TCP協(xié)議棧的相關(guān)函數(shù)我們都做了封裝,所以它們之間的調(diào)用都將以回調(diào)函數(shù)的方式實(shí)現(xiàn)。除了上述的軟件實(shí)現(xiàn)外,還需要注意必要的初始化配置。

3、應(yīng)用實(shí)現(xiàn)

??根據(jù)我們前面的設(shè)計,接下來我們考慮一下這一需求的具體實(shí)現(xiàn)過程。我們將這一過程分為4個部分來分別描述。

3.1、系統(tǒng)的初始化

??在實(shí)現(xiàn)具體的功能之前,我們需要對硬件以及軟件環(huán)境做必要的初始化配置。具體到這里就是對W5500作必要的軟硬件配置,包括接口、網(wǎng)絡(luò)以及回調(diào)函數(shù)等。具體實(shí)例代碼如下:

/* 以太網(wǎng)通訊配置 */
void McEthernetConfiguration(void)
{
  uint8_t mac[6]={0x01, 0x08, 0xdc,0x00, 0xab, 0xcd}; //本地Mac地址
  uint8_t ip[4]={192, 168, 1, 190};          //本地IP地址
  uint8_t sn[4]={255,255,255,0};           //子網(wǎng)掩碼
  uint8_t gw[4]={192, 168, 1, 1};           //網(wǎng)關(guān)地址
  uint8_t dns[4]={0,0,0,0};              //DNS服務(wù)器地址
  
  /* 以太網(wǎng)使用GPIO初始化 */
  GPIO_Init_Configuration();
  
  /* SPI1端口初始化 */
  SPI1_Init_Configuration();
  
  /*W5500對象初始化函數(shù)*/
  W5500Initialization(&w5500,      //W5500對象
                      mac,        //本地Mac地址
                      ip,        //本地IP地址
                      sn,        //子網(wǎng)掩碼
                      gw,        //網(wǎng)關(guān)地址
                      dns,        //DNS服務(wù)器地址
                      NETINFO_STATIC,  //DHCP類型
                      EnterCritical,   //進(jìn)入臨界區(qū)
                      ExitCritical,   //退出臨界區(qū)
                      EnableChipSelect, //片選使能
                      DisableChipSelect, //片選失能
                      ReadByteBySPI,   //SPI讀字節(jié)
                      WriteByteBySPI,  //SPI寫字節(jié)
                      W5500DataParsing, //報文解析函數(shù)
                      NULL        //數(shù)據(jù)請求函數(shù)
                      );
}

??在這個實(shí)例中,我們對網(wǎng)絡(luò)部分采用的是靜態(tài)配置,就是說網(wǎng)絡(luò)參數(shù)是固定不變的,而且我們的測試環(huán)境只限于局域網(wǎng)內(nèi)。

3.2、數(shù)據(jù)處理函數(shù)

??數(shù)據(jù)處理函數(shù)是最靈活的,因?yàn)槊總€項(xiàng)目及每個人對數(shù)據(jù)處理的要求都是不一樣的,只要能符合應(yīng)用要求就沒問題。需要說一下的是,這部分是Modbus協(xié)議棧對處理數(shù)據(jù)的要求,想要詳細(xì)了解的話,可以看我們以前關(guān)于Modbus協(xié)議站的文章。對于這個實(shí)例,數(shù)據(jù)處理函數(shù)如下:

/*獲取想要讀取的Coil量的值*/
void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList)
{
 uint16_t start;
 uint16_t count;

 /*先判斷地址是否處于合法范圍*/
 start=(startAddress>CoilStartAddress)?((startAddress<=CoilEndAddress)?startAddress:CoilEndAddress):CoilStartAddress;
 count=((start+quantity-1)<=CoilEndAddress)?quantity:(CoilEndAddress-start);
 
 for(int i=0;i/*獲取想要讀取的保持寄存器的值*/
void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)
{
 uint16_t start;
 uint16_t count;

 /*先判斷地址是否處于合法范圍*/
 start=(startAddress>HoldingRegisterStartAddress)?((startAddress<=HoldingRegisterEndAddress)?startAddress:HoldingRegisterEndAddress):HoldingRegisterStartAddress;
 count=((start+quantity-1)<=HoldingRegisterEndAddress)?quantity:(HoldingRegisterEndAddress-start);
 
 for(int i=0;i/*設(shè)置單個線圈的值*/
void SetSingleCoil(uint16_t coilAddress,bool coilValue)
{
 /*先判斷地址是否處于合法范圍*/
 if(coilAddress<=12)
 {
  dPara.coil[coilAddress]=coilValue;
 }
}
 
/*設(shè)置多個線圈的值*/
void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue)
{
 uint16_t endAddress=startAddress+quantity-1;
 if((startAddress<=12)&&(endAddress<=12))
 {
  for(int i=0;i/*設(shè)置單個寄存器的值*/
void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue)
{
 bool noError=(bool)(((50<=registerAddress)&&(registerAddress<=59))
                   ||((73<=registerAddress)&&(registerAddress<=74))
                   ||((90<=registerAddress)&&(registerAddress<=91)));

 if(noError)
 {
  aPara.holdingRegister[registerAddress]=registerValue;
 }
 
}
 
/*設(shè)置多個寄存器的值*/
void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)
{
 uint16_t endAddress=startAddress+quantity-1;
 
 bool noError=(bool)(((18<=startAddress)&&(startAddress<=28)&&(18<=endAddress)&&(endAddress<=28))
                   ||((50<=startAddress)&&(startAddress<=59)&&(50<=endAddress)&&(endAddress<=59))
                   ||((73<=startAddress)&&(startAddress<=74)&&(73<=endAddress)&&(endAddress<=74))
                   ||((90<=startAddress)&&(startAddress<=91)&&(90<=endAddress)&&(endAddress<=91)));
 if(noError)
 {
  for(int i=0;i

3.2、數(shù)據(jù)解析函數(shù)

??大家可能在前面的初始化函數(shù)中發(fā)現(xiàn)有一個名為W5500DataParsing的數(shù)據(jù)解析函數(shù)。這個函數(shù)是W5500驅(qū)動中,TCP服務(wù)器的要求,實(shí)現(xiàn)對數(shù)據(jù)的解析。因?yàn)榫唧w的應(yīng)用層協(xié)議解析多不勝數(shù),所以設(shè)計成了回調(diào)函數(shù),其函數(shù)原型如下:

/*解析接收到的數(shù)據(jù)*/
typedef uint16_t (*W5500DataParsingType)(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer);

??對于我們來說,我們需要根據(jù)具體的應(yīng)用層協(xié)議來實(shí)現(xiàn)這一函數(shù)。不過我們采用的Modbus TCP協(xié)議,在我們的Modbus協(xié)議棧中已經(jīng)實(shí)現(xiàn)了解析函數(shù),所以我們調(diào)用如下:

/*報文解析函數(shù)*/
static uint16_t W5500DataParsing(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer)
{
  /*解析接收到的信息,返回響應(yīng)命令的長度*/
  return ParsingClientAccessCommand(rxBuffer,txBuffer);
}

3.3、TCP服務(wù)器

??我們在前面已經(jīng)說過了,需要對服務(wù)器進(jìn)行輪詢。所以我們需要在一個進(jìn)程中輪詢訪問W5500的TCP服務(wù)器。同樣我們也要考慮多客戶端同時訪問的問題,我們將輪詢函數(shù)實(shí)現(xiàn)如下:

/* 以太網(wǎng)通訊處理 */
void McEthernetProcess(void)
{
  /*TCP服務(wù)器數(shù)據(jù)通訊*/
  W5500TCPServer(&w5500,Socket0,502);
  
  W5500TCPServer(&w5500,Socket1,503);
  
  W5500TCPServer(&w5500,Socket2,504);
  
  W5500TCPServer(&w5500,Socket3,505);
  
  W5500TCPServer(&w5500,Socket4,506);
  
  W5500TCPServer(&w5500,Socket5,507);
  
  W5500TCPServer(&w5500,Socket6,508);
  
  W5500TCPServer(&w5500,Socket7,509);
}

??事實(shí)上使用同一個Socket和不同的端口也是可以實(shí)現(xiàn)多客戶端訪問的,但既然有8個Socket,用起來自然更好一點(diǎn)。

4、應(yīng)用驗(yàn)證

??我們已經(jīng)根據(jù)需求實(shí)現(xiàn)了一個Modbus TCP服務(wù)器,究竟效果如何呢?我們還需要測試一下,以確認(rèn)設(shè)計的正確性。

4.1、通訊測試

??我們將目標(biāo)板連接到局域網(wǎng)中,使用著名的Modbus Poll軟件來測試一下我們設(shè)計的程序是否符合要求。

??我們首先在一臺機(jī)器上連接端口為504的Modbus TCP服務(wù)器,連接正常且數(shù)據(jù)獲取也完全正確。具體如下圖所示:

??同時,我們采用局域網(wǎng)內(nèi)的另一臺機(jī)器連接端口為502的Modbus TCP服務(wù)器,連接正常且數(shù)據(jù)獲取也完全正確。具體如下圖所示:

??經(jīng)過上述測試,我們可以確定我們實(shí)現(xiàn)的Modbus TCP服務(wù)器是可行的,而且在多客戶端并行訪問下也可以正確工作。

4.2、小結(jié)

??這一篇中,我們實(shí)現(xiàn)了可以支持多客戶端訪問的Modbus TCP服務(wù)器,經(jīng)測試運(yùn)行也符合設(shè)計預(yù)期。這里我們將需要考慮的幾個問題總結(jié)如下:

??關(guān)于初始化配置的問題,在這個例子中,我們對網(wǎng)絡(luò)的配置是直接在軟件上固定死的,這樣做雖然簡單直接但并不是一個好的選擇。更好的辦法是可以讓使用者自己配置,方法有多種,可以根據(jù)自己的實(shí)際情況,在軟件上進(jìn)一步的考慮。

??關(guān)于數(shù)據(jù)處理的問題,具體的數(shù)據(jù)處理與實(shí)際的應(yīng)用需求有關(guān),也與應(yīng)用層協(xié)議的要求有關(guān),這個例子中實(shí)現(xiàn)的Modbus的數(shù)據(jù)處理函數(shù)并不是唯一的,但可參考其思路。

??關(guān)于數(shù)據(jù)解析的問題,在本例中實(shí)現(xiàn)的是Modbus TCP服務(wù)器的解析函數(shù)。對于不同的應(yīng)用協(xié)議需要編寫不同的解析函數(shù),這部分是靈活性最大的,支持所有可運(yùn)行于TCP應(yīng)用層的通訊協(xié)議。

??關(guān)于多客戶端訪問的問題,W5500可以實(shí)現(xiàn)8個Socket,而Modbus TCP默認(rèn)端口號是502,當(dāng)然也可以使用其它端口。所以我們可以考慮使用不同的Socket和不同的端口號來實(shí)現(xiàn)并發(fā)訪問。事實(shí)上,經(jīng)過我們測試使用同一個Socket和不同的端口也是可以實(shí)現(xiàn)多客戶端訪問的,有興趣的同仁可以試試。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • MODBUS
    +關(guān)注

    關(guān)注

    28

    文章

    1805

    瀏覽量

    76986
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    9149

    瀏覽量

    85402
  • TCP
    TCP
    +關(guān)注

    關(guān)注

    8

    文章

    1353

    瀏覽量

    79068
  • ModBus協(xié)議
    +關(guān)注

    關(guān)注

    3

    文章

    177

    瀏覽量

    33423
  • W5500
    +關(guān)注

    關(guān)注

    5

    文章

    45

    瀏覽量

    17589
收藏 人收藏

    評論

    相關(guān)推薦

    W5500 keep-alive的用途與用法

    本帖最后由 Katrina_WIZnet 于 2015-9-2 14:50 編輯 大家是否遇到過這樣的問題,W5500作為服務(wù)器已經(jīng)建立連接,突然網(wǎng)線掉了,然后再去連接W5500,就連
    發(fā)表于 08-04 09:44

    WIZnet W5500的特點(diǎn)和應(yīng)用介紹

    `W5500是一款全硬件TCP/IP嵌入式以太網(wǎng)控制,為嵌入式系統(tǒng)提供了更加簡易的互聯(lián)網(wǎng)連接方案。W5500集成了TCP/IP協(xié)議棧,10
    發(fā)表于 05-05 13:28

    野火STM32+野火W5500實(shí)現(xiàn)服務(wù)器功能的問題

    `急!急!急!求大神相助!我用野火STM32+野火W5500實(shí)現(xiàn)服務(wù)器的功能,上位機(jī)通過IE輸入W5500的IP地址,W5500返回給上位機(jī)一個點(diǎn)亮LED的頁面,現(xiàn)在有“點(diǎn)亮LED”和
    發(fā)表于 11-18 21:47

    W5500無法連接服務(wù)器

    我的W5500能PING通,可是就是沒辦法連接服務(wù)器,究竟是什么原因呢?
    發(fā)表于 06-25 10:26

    W5500芯片通信問題

    w5500網(wǎng)絡(luò)通信問題:電腦做服務(wù)器,w5500做客戶端,自發(fā)自收,500ms一次,剛開始正常,過一會電腦端收到的數(shù)據(jù)就不對了,如圖:可能是哪兒的問題, 程序里面的接收buff嗎
    發(fā)表于 12-11 21:23

    W5500接收巨型數(shù)據(jù)包有什么好用的辦法

    當(dāng)W5500作為客戶端利用TCP協(xié)議接收遠(yuǎn)大于自身緩存的數(shù)據(jù)包時,服務(wù)器一次性發(fā)送一個幾十M為單位的文件,依靠TCP/IP協(xié)議棧維護(hù)數(shù)據(jù)收發(fā)的情況下,如何能保障接收的可靠性? 客戶端有
    發(fā)表于 06-05 18:44

    W5500TCP Client模式下,斷電重啟之后無法立即連接到服務(wù)器

    `分析:這是由于客戶端沒有主動發(fā)送斷開請求,造成服務(wù)器并不知道Socket已發(fā)生異常斷開; 重新上電之后,芯片以相同的IP和端口連接服務(wù)器,而服務(wù)器還認(rèn)為此Socket鏈接存在,所以拒絕芯片的立即
    發(fā)表于 05-04 16:54

    基于FPGA和W5500的以太網(wǎng)傳輸系統(tǒng)實(shí)現(xiàn)

    協(xié)議數(shù)據(jù)量,從而提高服務(wù)器性能。TOE方式不需進(jìn)行軟件協(xié)議棧移植,開發(fā)周期縮短,CPU負(fù)擔(dān)降低,穩(wěn)定性能提高。其處理方式框圖如下圖1所示。圖 1 TOE方式進(jìn)行TCP/IP協(xié)議棧處理2.2 W5500
    發(fā)表于 08-07 10:10

    STM32 W5500是如何提交數(shù)據(jù)到遠(yuǎn)程服務(wù)器

    STM32 W5500是如何提交數(shù)據(jù)到遠(yuǎn)程服務(wù)器的?如何去實(shí)現(xiàn)呢?
    發(fā)表于 11-26 07:43

    硬件SPI驅(qū)動W5500網(wǎng)口

    我有一塊STM32F303板子,用它驅(qū)動SPI接口W5500網(wǎng)口,硬件SPI. 現(xiàn)在TCP服務(wù)器,UDP都好了,就差TCP客戶端了。以前用其他單片機(jī)寫這個程序
    發(fā)表于 06-12 20:21

    w5500原理圖_w5500電路圖

    W5500是WIZnet推出的高性能以太網(wǎng)接口芯片系列之一,內(nèi)部集成全硬件TCP/IP協(xié)議棧+MAC+PHY。全硬件協(xié)議棧技術(shù)采用硬件邏輯門電路實(shí)現(xiàn)復(fù)雜的TCP/IP協(xié)議簇,其應(yīng)用具有簡單快速
    發(fā)表于 10-23 19:14 ?5.5w次閱讀
    <b class='flag-5'>w5500</b>原理圖_<b class='flag-5'>w5500</b>電路圖

    W5500數(shù)據(jù)手冊免費(fèi)下載

    W5500芯片是硬連線的TCP / IP嵌入式以太網(wǎng)控制,可提供與嵌入式系統(tǒng)的Internet連接更加輕松。 W5500使用戶能夠擁有只需使用其中的單個芯片即可在其應(yīng)用程序中實(shí)現(xiàn)In
    發(fā)表于 05-18 11:26 ?28次下載

    RaspberryPi Pico Web服務(wù)器w5100S(W5500)

    電子發(fā)燒友網(wǎng)站提供《RaspberryPi Pico Web服務(wù)器w5100S(W5500).zip》資料免費(fèi)下載
    發(fā)表于 11-10 14:48 ?1次下載
    RaspberryPi Pico Web<b class='flag-5'>服務(wù)器</b><b class='flag-5'>w</b>5100S(<b class='flag-5'>W5500</b>)

    W5500 數(shù)據(jù)手冊中文資料

    W5500是一款全硬件 TCP/IP 嵌入式以太網(wǎng)控制,為嵌入式系統(tǒng)提供了更加簡易的互聯(lián)網(wǎng)連接方案。W5500集成了TCP/IP 協(xié)議棧,
    發(fā)表于 01-06 16:58 ?19次下載

    W5500瀏覽配置_Keil

    W5500瀏覽配置_Keil,瀏覽配置就是在電路板上搭載一個嵌入式的web服務(wù)器,所以功能一般的很有限。
    發(fā)表于 01-07 14:14 ?3次下載