1. 程式人生 > >【STM32乙太網線上培訓】手把手搭建TCP伺服器及TFTP伺服器

【STM32乙太網線上培訓】手把手搭建TCP伺服器及TFTP伺服器

非常感謝STM32乙太網線上培訓,真的讓我學到非常多,沒培訓前很想搞乙太網但有種無從下手的感覺,經過這次培訓讓我從這個架構上有個從上到下的瞭解,再借助官方神器STM32CubeMX,開發和學習起來還是非常快的!我一直是STM32的粉絲,一直想去現場培訓,但無奈沒有機會,不過好希望能申請一個板子
  看培訓直播的照片我就不發了,當時看這個直播喝水都是跑著去的。
  我手上沒有官方的板子,帶乙太網的只有原子的F407的板子,所以也是在這個平臺上做的,板子上的PHY為LAN8720A,沒有用到顯示屏全為串列埠輸出除錯資訊。
實現功能:通過STM32CubeMX在原子F407板子上搭建TCP Server 及 TFTP Server ,實現這些功能非常簡單。
一、功能介紹
  1、TCP Server 實現顯示連線上的客戶端IP及對客戶端發來的資料回傳
  2、TFTP Server 實現IAP功能及讀取MCU內部FLASH資料。這裡用到的是檔案傳輸協議,主要是參考官方文件UM1709,及官方STM324xG_EVAL的LWIP IAP例子。
二、工程搭建
1、外設配置開啟STM32CubeMX,點NEW PROJECT,在左側的輸入框中輸入407ZG,在右側會顯示STM32F407ZGT6的MCU列表,選擇此列表,雙擊。
設定時鐘源為外部時鐘

開啟SWD除錯介面,開啟ETH外設,選擇RMII介面(LAN8720A為RMII介面),中介軟體裡時能LWIP。使用RMII介面的時候軟體會自動配置對應的引腳,但是一定要仔細對比軟體配置的引腳是否為板子上硬體所連線的。


原子F407板子上的LAN8720A的RMII介面引腳如下圖,
 
而軟體配置的引腳如下:
 
顯然與實際板子上的硬體連線不同,這裡需要手動調整3個引腳,分別為:
  ETH_TX_EN -> PG11
  ETH_TXD0  ->  PG13
  ETH_TXD1  ->  PG14
  PD3配置成GPIO輸出(LAN8720A的硬體復位引腳)
時能串列埠1,用於除錯
 
2、配置時鐘
外部高速晶振選擇為8M,PLL SORCE MUX 選擇為HSE,在HCLK處輸入168點回車,軟體會自動配置好。

 


3、中介軟體配置
這裡我們主要配置,ETH、LWIP、串列埠1、GPIO


因為PD3為PHY的輸入引腳,所有我們這裡直接配置它為輸出高,這樣就為正常工作狀態
 
串列埠1我們設定它的波特率為115200,添加發送DMA,注意!如果添加了傳送或者接收DMA則必須開啟串列埠中斷。
 
ETH設定,這裡主要是注意PHY的地址,原子407的PHY地址引腳為懸空,地址為0。
 
ETH設定的 Advanced Parameters 選項裡面,我們選擇PHY為USER PHY,名字我們取 LAN7820A,其它的設定全用預設即可。要改的暫存器基本就只有ExternalPHY Configuration,但是我看了下官方的例子,基本沒有用到這暫存器裡面的值,只有在使用作業系統並且時能了連線狀態變更回撥或者其它檢測的時候才用到,所有這裡我們也不管,都用預設值。


 
LWIP設定 ,我們關閉DHCP,採用靜態IP,因為我所在的閘道器為192.168.0.XXX,所以IP必須為192.168.0.XXX。這裡我設定IP為192.168.0.120。因為要用到TCP 和 UDP(TFTP就是用的UDP),所以這兩個都是時能的.其它設定選項我們先不管,直接點OK。 

4、生成工程程式碼
點左上角的Project,選擇Settings ...(不建議直接點黃色的齒輪)


取個非中文的工程名,選擇IDE為KEIL MDK V5
 

點選OK。再點右上方的黃色齒輪即可生成工程程式碼。
三、工程程式碼
生成的程式碼的試圖如下:
 
1、TCP Server 的實現
在math.h中 加入
#include "stm32f4xx_hal.h"
#include "stdio.h"

用於支援printf 及 一些 HAL 定義的 資料結構

在uart.c 檔案中加入如下程式碼,用於把printf輸出到串列埠1
  1. /* USER CODE BEGIN 0 */
  2. int fputc(int c,FILE * f)
  3. {
  4. HAL_UART_Transmit(&huart1,(uint8_t *)&c,1,20);
  5. return c;
  6. }
  7. /* USER CODE END 0 */
複製程式碼注意一定要在 /* USER CODE BEGIN X */ 與 /* USER CODE END X */ 中間新增程式碼,不然重新用STM32CubeMX生成程式碼後就會被覆蓋。
在LWIP.C 中新增如下程式碼:
  1. void User_notification(void) 
  2. {
  3. if (netif_is_up(&gnetif))
  4. {
  5.     uint8_t iptxt[20];
  6.     sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&gnetif.ip_addr));
  7.     printf ("Static IP address: %s\r\n", iptxt);
  8. }
  9. }
複製程式碼這個程式碼的功能是把網絡卡的IP地址通過串列埠輸出,這個是從官方例子裡面COPY出來的,例子裡面是輸出到LCD上,這個是通過串列埠輸出
接下來重點:
新建 tcp_echoserver.c 檔案,並在檔案中新增如下程式碼:
  1. /**
  2. * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
  3. * All rights reserved. 
  4. * Redistribution and use in source and binary forms, with or without modification, 
  5. * are permitted provided that the following conditions are met:
  6. *
  7. * 1. Redistributions of source code must retain the above copyright notice,
  8. *    this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright notice,
  10. *    this list of conditions and the following disclaimer in the documentation
  11. *    and/or other materials provided with the distribution.
  12. * 3. The name of the author may not be used to endorse or promote products
  13. *    derived from this software without specific prior written permission. 
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
  16. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
  17. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
  18. * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  19. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
  20. * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
  21. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  22. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
  23. * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
  24. * OF SUCH DAMAGE.
  25. *
  26. * This file is part of and a contribution to the lwIP TCP/IP stack.
  27. *
  28. * Credits go to Adam Dunkels (and the current maintainers) of this software.
  29. *
  30. * Christiaan Simons rewrote this file to get a more stable echo application.
  31. *
  32. **/
  33.  
  34. /* This file was modified by ST */
  35.  
  36.  
  37. #include "lwip/debug.h"
  38. #include "lwip/stats.h"
  39. #include "lwip/tcp.h"
  40. #include "usart.h"
  41.  
  42. #if LWIP_TCP
  43.  
  44. static struct tcp_pcb *tcp_echoserver_pcb;
  45.  
  46. /* ECHO protocol states */
  47. enum tcp_echoserver_states
  48. {
  49.   ES_NONE = 0,
  50.   ES_ACCEPTED,
  51.   ES_RECEIVED,
  52.   ES_CLOSING
  53. };
  54.  
  55. /* structure for maintaing connection infos to be passed as argument 
  56.    to LwIP callbacks*/
  57. struct tcp_echoserver_struct
  58. {
  59.   u8_t state;             /* current connection state */
  60.   u8_t retries;
  61.   struct tcp_pcb *pcb;    /* pointer on the current tcp_pcb */
  62.   struct pbuf *p;         /* pointer on the received/to be transmitted pbuf */
  63. };
  64.  
  65.  
  66. static err_t tcp_echoserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
  67. static err_t tcp_echoserver_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
  68. static void tcp_echoserver_error(void *arg, err_t err);
  69. static err_t tcp_echoserver_poll(void *arg, struct tcp_pcb *tpcb);
  70. static err_t tcp_echoserver_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
  71. static void tcp_echoserver_send(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es);
  72. static void tcp_echoserver_connection_close(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es);
  73.  
  74.  
  75. /**
  76.   * @brief  Initializes the tcp echo server
  77.   * @param  None
  78.   * @retval None
  79.   */
  80. void tcp_echoserver_init(void)
  81. {
  82.   /* create new tcp pcb */
  83.   tcp_echoserver_pcb = tcp_new();
  84.  
  85.   if (tcp_echoserver_pcb != NULL)
  86.   {
  87.     err_t err;
  88.     
  89.     /* bind echo_pcb to port 7 (ECHO protocol) */
  90.     err = tcp_bind(tcp_echoserver_pcb, IP_ADDR_ANY, 7);
  91.     
  92.     if (err == ERR_OK)
  93.     {
  94.       /* start tcp listening for echo_pcb */
  95.       tcp_echoserver_pcb = tcp_listen(tcp_echoserver_pcb);
  96.       printf("開始監聽 \r\n");
  97.       /* initialize LwIP tcp_accept callback function */
  98.       tcp_accept(tcp_echoserver_pcb, tcp_echoserver_accept);
  99.       printf("掛載客戶端連接回調函式 \r\n");
  100.     }
  101.     else 
  102.     {
  103.       /* deallocate the pcb */
  104.       memp_free(MEMP_TCP_PCB, tcp_echoserver_pcb);
  105.       printf("TCP PCB 記憶體申請失敗 \r\n");
  106.     }
  107.   }
  108. }
  109.  
  110. /**
  111.   * @brief  This function is the implementation of tcp_accept LwIP callback
  112.   * @param  arg: not used
  113.   * @param  newpcb: pointer on tcp_pcb struct for the newly created tcp connection
  114.   * @param  err: not used 
  115.   * @retval err_t: error status
  116.   */
  117. static err_t tcp_echoserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
  118. {
  119.   err_t ret_err;
  120.   struct tcp_echoserver_struct *es;
  121.  
  122.   LWIP_UNUSED_ARG(arg);
  123.   LWIP_UNUSED_ARG(err);
  124.  
  125.   /* set priority for the newly accepted tcp connection newpcb */
  126.   tcp_setprio(newpcb, TCP_PRIO_MIN);
  127.   printf("收到客戶端連線請求,設定剛連線的客戶端為最低優先順序 \r\n");
  128.     
  129.   uint8_t iptxt[20];
  130.   sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&newpcb->remote_ip));
  131.   printf ("客戶端 IP address: %s\r\n", iptxt); 
  132.     
  133.   /* allocate structure es to maintain tcp connection informations */
  134.   es = (struct tcp_echoserver_struct *)mem_malloc(sizeof(struct tcp_echoserver_struct));
  135.   if (es != NULL)
  136.   {
  137.     es->state = ES_ACCEPTED;
  138.     es->pcb = newpcb;
  139.     es->retries = 0;
  140.     es->p = NULL;
  141.       
  142.     printf("為新連線的客戶端掛載需要的回撥函式及 呼叫引數 \r\n");
  143.       
  144.     /* pass newly allocated es structure as argument to newpcb */
  145.     tcp_arg(newpcb, es);
  146.     
  147.     /* initialize lwip tcp_recv callback function for newpcb  */ 
  148.     tcp_recv(newpcb, tcp_echoserver_recv);
  149.     
  150.     /* initialize lwip tcp_err callback function for newpcb  */
  151.     tcp_err(newpcb, tcp_echoserver_error);
  152.     
  153.     /* initialize lwip tcp_poll callback function for newpcb */
  154.     tcp_poll(newpcb, tcp_echoserver_poll, 0);  
  155.     ret_err = ERR_OK;
  156.   }
  157.   else
  158.   {
  159.     /*  close tcp connection */
  160.     tcp_echoserver_connection_close(newpcb, es);
  161.     printf("tcp_echoserver_struct 記憶體申請失敗 關閉連線 \r\n");
  162.     /* return memory error */
  163.     ret_err = ERR_MEM;
  164.   }
  165.   return ret_err;  
  166. }
  167.  
  168.  
  169. /**
  170.   * @brief  This function is the implementation for tcp_recv LwIP callback
  171.   * @param  arg: pointer on a argument for the tcp_pcb connection
  172.   * @param  tpcb: pointer on the tcp_pcb connection
  173.   * @param  pbuf: pointer on the received pbuf
  174.   * @param  err: error information regarding the reveived pbuf
  175.   * @retval err_t: error code
  176.   */
  177. static err_t tcp_echoserver_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
  178. {
  179.   struct tcp_echoserver_struct *es;
  180.   err_t ret_err;
  181.  
  182.   LWIP_ASSERT("arg != NULL",arg != NULL);
  183.   printf("收到客戶端資料\r\n");
  184.   es = (struct tcp_echoserver_struct *)arg;
  185.   
  186.   /* if we receive an empty tcp frame from client => close connection */
  187.   if (p == NULL)
  188.   {
  189.     printf("收到斷開連線請求 \r\n");
  190.     /* remote host closed connection */
  191.     es->state = ES_CLOSING;
  192.     if(es->p == NULL)
  193.     {
  194.        /* we're done sending, close connection */
  195.        tcp_echoserver_connection_close(tpcb, es);
  196.     }
  197.     else
  198.     {
  199.        printf("傳送的資料還未傳送完成 \r\n");
  200.       /* we're not done yet */
  201.       /* acknowledge received packet */
  202.       tcp_sent(tpcb, tcp_echoserver_sent);
  203.       printf("裝載傳送完成回撥函式 \r\n");
  204.       /* send remaining data*/
  205.       tcp_echoserver_send(tpcb, es);
  206.     }
  207.     ret_err = ERR_OK;
  208.   }   
  209.   /* else : a non empty frame was received from client but for some reason err != ERR_OK */
  210.   else if(err != ERR_OK)
  211.   {
  212.     /* free received pbuf*/
  213.     if (p != NULL)
  214.     {
  215.       es->p = NULL;
  216.       pbuf_free(p);
  217.     }
  218.     ret_err = err;
  219.   }
  220.   else if(es->state == ES_ACCEPTED)
  221.   {
  222.     /* first data chunk in p->payload */
  223.     es->state = ES_RECEIVED;
  224.     
  225.     /* store reference to incoming pbuf (chain) */
  226.     es->p = p;
  227.     
  228.     /* initialize LwIP tcp_sent callback function */
  229.     tcp_sent(tpcb, tcp_echoserver_sent);
  230.     printf("裝置剛連線,掛載剛才接收到的資料,設定傳送完成回撥 \r\n");
  231.     /* send back the received data (echo) */
  232.     tcp_echoserver_send(tpcb, es);
  233.     
  234.     ret_err = ERR_OK;
  235.   }
  236.   else if (es->state == ES_RECEIVED)
  237.   {
  238.     /* more data received from client and previous data has been already sent*/
  239.     if(es->p == NULL)
  240.     {
  241.       es->p = p;
  242.       printf("接收到的資料直接回傳 \r\n");
  243.       /* send back received data */
  244.       tcp_echoserver_send(tpcb, es);
  245.     }
  246.     else
  247.     {
  248.       struct pbuf *ptr;
  249.       printf("上次的資料還未傳送完成,把新資料拼接在後面 \r\n");
  250.       /* chain pbufs to the end of what we recv'ed previously  */
  251.       ptr = es->p;
  252.       pbuf_chain(ptr,p);
  253.     }
  254.     ret_err = ERR_OK;
  255.   }
  256.   else if(es->state == ES_CLOSING)
  257.   {
  258.     printf("當前已經是關閉連線了,但還是受到資料 \r\n");
  259.     /* odd case, remote side closing twice, trash data */
  260.     tcp_recved(tpcb, p->tot_len);
  261.     es->p = NULL;
  262.     pbuf_free(p);
  263.     ret_err = ERR_OK;
  264.   }
  265.   else
  266.   {
  267.     /* unkown es->state, trash data  */
  268.     tcp_recved(tpcb, p->tot_len);
  269.     es->p = NULL;
  270.     pbuf_free(p);
  271.     ret_err = ERR_OK;
  272.   }
  273.   return ret_err;
  274. }
  275.  
  276. /**
  277.   * @brief  This function implements the tcp_err callback function (called
  278.   *         when a fatal tcp_connection error occurs. 
  279.   * @param  arg: pointer on argument parameter 
  280.   * @param  err: not used
  281.   * @retval None
  282.   */
  283. static void tcp_echoserver_error(void *arg, err_t err)
  284. {
  285.   struct tcp_echoserver_struct *es;
  286.  
  287.   LWIP_UNUSED_ARG(err);
  288.   printf("錯誤 : %d \r\n",err);
  289.   es = (struct tcp_echoserver_struct *)arg;
  290.   if (es != NULL)
  291.   {
  292.     /*  free es structure */
  293.     mem_free(es);
  294.   }
  295. }
  296.  
  297. /**
  298.   * @brief  This function implements the tcp_poll LwIP callback function
  299.   * @param  arg: pointer on argument passed to callback
  300.   * @param  tpcb: pointer on the tcp_pcb for the current tcp connection
  301.   * @retval err_t: error code
  302.   */
  303. static err_t tcp_echoserver_poll(void *arg, struct tcp_pcb *tpcb)
  304. {
  305.   err_t ret_err;
  306.   struct tcp_echoserver_struct *es;
  307.  
  308.   es = (struct tcp_echoserver_struct *)arg;
  309.   if (es != NULL)
  310.   {
  311.     if (es->p != NULL)
  312.     {
  313.       tcp_sent(tpcb, tcp_echoserver_sent);
  314.       /* there is a remaining pbuf (chain) , try to send data */
  315.       tcp_echoserver_send(tpcb, es);
  316.     }
  317.     else
  318.     {
  319.       /* no remaining pbuf (chain)  */
  320.       if(es->state == ES_CLOSING)
  321.       {
  322.         /*  close tcp connection */
  323.         tcp_echoserver_connection_close(tpcb, es);
  324.       }
  325.     }
  326.     ret_err = ERR_OK;
  327.   }
  328.   else
  329.   {
  330.     /* nothing to be done */
  331.     tcp_abort(tpcb);
  332.     ret_err = ERR_ABRT;
  333.   }
  334.   return ret_err;
  335. }
  336.  
  337. /**
  338.   * @brief  This function implements the tcp_sent LwIP callback (called when ACK
  339.   *         is received from remote host for sent data) 
  340.   * @param  None
  341.   * @retval None
  342.   */
  343. static err_t tcp_echoserver_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
  344. {
  345.   struct tcp_echoserver_struct *es;
  346.  
  347.   LWIP_UNUSED_ARG(len);
  348.  
  349.   es = (struct tcp_echoserver_struct *)arg;
  350.   es->retries = 0;
  351.   
  352.   if(es->p != NULL)
  353.   {
  354.     /* still got pbufs to send */
  355.     tcp_sent(tpcb, tcp_echoserver_sent);
  356.     tcp_echoserver_send(tpcb, es);
  357.   }
  358.   else
  359.   {
  360.     /* if no more data to send and client closed connection*/
  361.     if(es->state == ES_CLOSING)
  362.       tcp_echoserver_connection_close(tpcb, es);
  363.   }
  364.   return ERR_OK;
  365. }
  366.  
  367.  
  368. /**
  369.   * @brief  This function is used to send data for tcp connection
  370.   * @param  tpcb: pointer on the tcp_pcb connection
  371.   * @param  es: pointer on echo_state structure
  372.   * @retval None
  373.   */
  374. static void tcp_echoserver_send(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es)
  375. {
  376.   struct pbuf *ptr;
  377.   err_t wr_err = ERR_OK;
  378.   uint16_t Count ;
  379.   printf("傳送資料的總長度 : %ld \r\n",es->p->tot_len);
  380.   printf("傳送資料: \r\n");
  381.   while ((wr_err == ERR_OK) &&
  382.          (es->p != NULL) && 
  383.          (es->p->len <= tcp_sndbuf(tpcb)))
  384.   {
  385.     
  386.     /* get pointer on pbuf from es structure */
  387.     ptr = es->p; //得到當前需要傳送的資料快取 pbuf
  388.  
  389.     for( Count =0 ; Count < ptr->len; Count++ )
  390.     {
  391.       printf("0X%02X ",((uint8_t *)ptr->payload)[Count]);
  392.     }
  393.     /* enqueue data for transmission */
  394.     wr_err = tcp_write(tpcb, ptr->payload, ptr->len, TCP_WRITE_FLAG_COPY);  //傳送
  395.     
  396.     if (wr_err == ERR_OK)
  397.     {
  398.       u16_t plen;
  399.       u8_t freed;
  400.  
  401.       plen = ptr->len; //得到當前節點的資料長度
  402.      
  403.       /* continue with next pbuf in chain (if any) */
  404.       es->p = ptr->next; //得到連結串列的下一個節點
  405.       
  406.       if(es->p != NULL)  //如果節點不為空
  407.       {
  408.         /* increment reference count for es->p */
  409.         pbuf_ref(es->p); //成員 ref 的值加1  當收到資料時,pbuf成員ref的值為1 ,在pbuf_free()函式中是對成員ref減一 如果結果為0則釋放此節點記憶體並進入下一個節點  否則只是把成員ref的值減一
  410.       }
  411.       
  412.      /* chop first pbuf from chain */
  413.       do
  414.       {
  415.         /* try hard to free pbuf */
  416.         freed = pbuf_free(ptr);  //每執行一個 pbuf中的成員ref的值減一  當它這個節點沒有釋放時,返回一直為0
  417.       }
  418.       while(freed == 0);  //執行直到釋放這個節點記憶體為止  pbuf_free()函式的返回值表示釋放的記憶體的節點數
  419.      /* we can read more data now */
  420.      tcp_recved(tpcb, plen);   //增加可以接收資料的大小
  421.    }
  422.    else if(wr_err == ERR_MEM)
  423.    {
  424.       /* we are low on memory, try later / harder, defer to poll */
  425.      es->p = ptr; //重發
  426.    }
  427.    else
  428.    {
  429.      /* other problem ?? */
  430.    }
  431.   }
  432.   printf("\r\n");
  433. }
  434.  
  435. /**
  436.   * @brief  This functions closes the tcp connection
  437.   * @param  tcp_pcb: pointer on the tcp connection
  438.   * @param  es: pointer on echo_state structure
  439.   * @retval None
  440.   */
  441. static void tcp_echoserver_connection_close(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es)
  442. {
  443.   
  444.   /* remove all callbacks */
  445.   tcp_arg(tpcb, NULL);
  446.   tcp_sent(tpcb, NULL);
  447.   tcp_recv(tpcb, NULL);
  448.   tcp_err(tpcb, NULL);
  449.   tcp_poll(tpcb, NULL, 0);
  450.   
  451.   /* delete es structure */
  452.   if (es != NULL)
  453.   {
  454.     mem_free(es);
  455.   }  
  456.   
  457.   /* close tcp connection */
  458.   tcp_close(tpcb);
  459.   printf("已關閉連線 \r\n");
  460. }
  461.  
  462. #endif /* LWIP_TCP */
複製程式碼這個程式碼也是從官方的 TCP echoserver 例子COPY出來的,不過裡面加入了串列埠輸出資訊,及一些註釋,感覺寫的很好的,就沒怎麼改了
看起來程式碼很多,起始真的不復雜,聽了之前的乙太網配置,這個理解起來還是很簡單的。大致流程如下,先新建一個TCB_PCB、開始監聽、掛載客戶端連線請求回撥函式、掛載接收資料回撥函式、掛載傳送完成回撥函式、掛載出錯回撥函式、掛載心跳包回撥函式、關閉連線等,最重要的是釋放記憶體!每收到一個數據鏈,它已經被申請了記憶體,所有在處理完這個資料後一定要釋放。
在mian.c檔案中加入如下程式碼:
 
編譯,下載到開發板中。
串列埠輸出如下資料,表示伺服器已經開始監聽
Start 
開始監聽 
掛載客戶端連接回調函式 
Static IP address: 192.168.0.120

點選電腦開始選單,輸入CMD點回車,出現命令列,在命令列中輸入 :"ping 192.168.0.120"
能ping通,顯示如下:
 
命令列使用技巧: 直接輸入 “help” 可以返回支援的命令, 在命令後面輸入  "/?"  可以返回命令的使用說明,比如輸入 “ping /?” 
2、TCP伺服器 測試
用網路除錯助手連線TCP伺服器(兩者必須在一個閘道器裡面,192.168.000.XXX),網路除錯助手裡選擇TCP客戶端,遠端主機IP為 192.168.0.120 , 埠號為 7,
 點選連線,串列埠除錯助手顯示如下資訊:
   Start 
   開始監聽 
   掛載客戶端連接回調函式 
   Static IP address: 192.168.0.120
   收到客戶端連線請求,設定剛連線的客戶端為最低優先順序 
   客戶端 IP address: 192.168.0.101
   為新連線的客戶端掛載需要的回撥函式及 呼叫引數 
則表示連線正常,這時網路除錯助手輸出什麼資料就會收到什麼資料,同時串列埠也會輸出相關資訊
網路除錯助手 輸出: http://www.cmsoft.cn QQ:10865600
網路除錯助手 顯示: 【Receive from 192.168.0.120 :7】:http://www.cmsoft.cnQQ:10865600

串列埠除錯助手顯示如下:
Start 
開始監聽 
掛載客戶端連接回調函式 
Static IP address: 192.168.0.120
收到客戶端連線請求,設定剛連線的客戶端為最低優先順序 
客戶端 IP address: 192.168.0.101
為新連線的客戶端掛載需要的回撥函式及 呼叫引數 
收到客戶端資料
裝置剛連線,掛載剛才接收到的資料,設定傳送完成回撥 
傳送資料的總長度 : 32 
傳送資料: 
0X68 0X74 0X74 0X70 0X3A 0X2F 0X2F 0X77 0X77 0X77 0X2E 0X63 0X6D 0X73 0X6F 0X66 0X74 0X2E 0X63 0X6E 0X20 0X51 0X51 0X3A 0X31 0X30 0X38 0X36 0X35 0X36 0X30 0X30 


網路除錯助手點選“斷開”按鍵,串列埠除錯助手輸出如下資訊:
Start 
開始監聽 
掛載客戶端連接回調函式 
Static IP address: 192.168.0.120
收到客戶端連線請求,設定剛連線的客戶端為最低優先順序 
客戶端 IP address: 192.168.0.101
為新連線的客戶端掛載需要的回撥函式及 呼叫引數 
收到客戶端資料
裝置剛連線,掛載剛才接收到的資料,設定傳送完成回撥 
傳送資料的總長度 : 32 
傳送資料: 
0X68 0X74 0X74 0X70 0X3A 0X2F 0X2F 0X77 0X77 0X77 0X2E 0X63 0X6D 0X73 0X6F 0X66 0X74 0X2E 0X63 0X6E 0X20 0X51 0X51 0X3A 0X31 0X30 0X38 0X36 0X35 0X36 0X30 0X30 
收到客戶端資料
收到斷開連線請求 
已關閉連線 


通過上面的測試,則表示我們的TCP伺服器已經完成。您也可以通過手機連線這個路由器的WIFI,通過 網路除錯助手APP 來連線這個開發板的伺服器,電腦可以通過 TCP客戶端與開發板的TCP伺服器連線同時也可以用手機做TCP客戶端連線開發板的TCP伺服器。


3、TFTP Server 的實現
還是在上面的程式碼上來實現此功能,即在TCP 伺服器的程式碼上實現 TFTP 伺服器。
開啟剛才STM32CubeMX工程,在原來的基礎上來配置 LWIP,配置如下,時能TFTP
 
點選OK,然後點黃色的齒輪,重新生成程式碼。因為我們之前寫的程式碼要麼是在 /* USER CODE BEGIN X */ 與 /* USER CODE END X */ 中間新增程式碼要麼是在新建的檔案中新增程式碼,所以不會覆蓋之前的程式碼。


生成的程式碼檢視如下:
 

注意到了嗎?LWIP資料夾下多了一個新的.c,tftp_server.c,既然是在LWIP資料夾下,言外之意就是別人幫我們寫好了,不要動這裡面的檔案內容,相當於庫,但是我們還是要知道去怎麼用。這裡我們之間偷懶,直接開啟tptp_server.h,因為如果要給別的檔案提供功能函式,那麼必須會在.h檔案中存在宣告。開啟tptp_server.h檔案,內容如下:
  1. /** @ingroup tftp
  2. * TFTP context containing callback functions for TFTP transfers
  3. */
  4. struct tftp_context {
  5.   /**
  6.    * Open file for read/write.
  7.    * @param fname Filename
  8.    * @param mode Mode string from TFTP RFC 1350 (netascii, octet, mail)
  9.    * @param write Flag indicating read (0) or write (!= 0) access
  10.    * @returns File handle supplied to other functions
  11.    */
  12.   void* (*open)(const char* fname, const char* mode, u8_t write);
  13.   /**
  14.    * Close file handle
  15.    * @param handle File handle returned by open()
  16.    */
  17.   void (*close)(void* handle);
  18.   /**
  19.    * Read from file 
  20.    * @param handle File handle returned by open()
  21.    * @param buf Target buffer to copy read data to
  22.    * @param bytes Number of bytes to copy to buf
  23.    * @returns >= 0: Success; < 0: Error
  24.    */
  25.   int (*read)(void* handle, void* buf, int bytes);
  26.   /**
  27.    * Write to file
  28.    * @param handle File handle returned by open()
  29.    * @param pbuf PBUF adjusted such that payload pointer points
  30.    *             to the beginning of write data. In other words,
  31.    *             TFTP headers are stripped off.
  32.    * @returns >= 0: Success; < 0: Error
  33.    */
  34.   int (*write)(void* handle, struct pbuf* p);
  35. };
  36.  
  37. err_t tftp_init(const struct tftp_context* ctx);
複製程式碼

裡面就一個 err_t tftp_init(const struct tftp_context* ctx);  從函式名的意思就是初始化,形參是一個結構體指標,而這個結構體裡面定義的全是函式指標,那麼很明確了,這個檔案就是要我們填寫輪子。
我們新建一個檔案 mytftpserverif.c ,這個檔案裡面的內容 就是用來寫那些輪子的實現函式,內容如下:
  1. #include "mytftpserverif.h"
  2. #include <string.h>
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "flash_if.h"
  6. #include "stdlib.h"
  7. #include "string.h"
  8.  
  9.  
  10. __packed typedef struct 
  11. {
  12.   uint8_t   InitFlat;
  13.   uint8_t   W_R_Mode;   //讀寫模式   1寫  0讀
  14.   uint32_t  Flash_Addr ;
  15. }Iap_FlashCont;
  16.  
  17.  
  18. static  Iap_FlashCont   Iapflashcont ;
  19.  
  20.  
  21.  
  22. static void * OpenFile(const char* fname, const char* mode, u8_t write);
  23. static void Close_File(void* handle);
  24. static int Read_File(void* handle, void* buf, int bytes);
  25. static int Write_File(void* handle, struct pbuf* p);
  26.  
  27. const struct tftp_context  tftpContext={       //TFTP SERVER 對接介面
  28. OpenFile,
  29. Close_File,
  30. Read_File,    
  31. Write_File,
  32. };
  33.  
  34.  
  35.   /**
  36.    *  開啟檔案  返回檔案控制代碼
  37.    * @param const char* fname   檔名
  38.    * @param const char* mode
  39.    * @param u8_t write   模式  1寫  0讀
  40.    * @returns 檔案控制代碼
  41.   */
  42. static void * OpenFile(const char* fname, const char* mode, u8_t write)
  43. {   
  44.   printf("開啟檔案  %s \r\n",fname);    
  45.   printf("開啟模式  %s \r\n",mode);
  46.   Iapflashcont.W_R_Mode = write ;
  47.   Iapflashcont.W_R_Mode ? printf("寫檔案\r\n") : printf("讀檔案\r\n");
  48.  
  49.   if(Iapflashcont.W_R_Mode == 1)
  50.   {
  51.     FLASH_If_Init();   //解鎖
  52. Iapflashcont.Flash_Addr = USER_FLASH_FIRST_PAGE_ADDRESS ;  //FLASH起始地址
  53.     printf("開始擦除Flash  時間較長 \r\n");  
  54.     if( FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS) == 0 )   //擦除使用者區FLASH資料
  55.     {
  56.       Iapflashcont.InitFlat =1 ;   //標記初始化完成
  57.       printf("Write File Init Succes \r\n");
  58.     }
  59.   } //如果為讀檔案模式
  60.   else if(memcmp(fname,"flash.bin",strlen("flash.bin"))==0)  //可以讀內部FLASH
  61.   {
  62.     Iapflashcont.InitFlat =1 ;   //標記初始化完成
  63.     printf("Read File Init Succes \r\n");
  64.     Iapflashcont.Flash_Addr = FLASH_BASE ;  //FLASH起始地址
  65.   }
  66.   return (Iapflashcont.InitFlat) ? (&Iapflashcont) : NULL ;  //如果初始化成功  返回有效控制代碼
  67. }
  68.  
  69.   /**
  70.    *  關閉檔案控制代碼
  71.    * @param None
  72.    * @param None
  73.    * @param None
  74.    * @returns None
  75.    */
  76. static void Close_File(void* handle)
  77. {
  78.   Iap_FlashCont * Filehandle = (Iap_FlashCont *) handle ;
  79.      
  80.   FLASH_If_UnInit();  //FLASH上鎖
  81.   Filehandle->InitFlat = 0 ;
  82.   printf("關閉檔案\r\n");  
  83.   if(Filehandle->W_R_Mode)  //如果之前是寫檔案
  84.   {
  85.     typedef  void (*pFunction)(void);
  86.     pFunction Jump_To_Application;
  87.     uint32_t JumpAddress;
  88.     /* Check if valid stack address (RAM address) then jump to user application */
  89.    if (((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
  90.    {
  91.      /* Jump to user application */
  92.      JumpAddress = *(__IO uint32_t*) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
  93.      Jump_To_Application =   (pFunction)JumpAddress;
  94.      /* Initialize user application's Stack Pointer */
  95.      __set_MSP(*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS);
  96.      Jump_To_Application();
  97.      /* do nothing */
  98.      while(1);
  99.    }
  100.   }
  101. }
  102.  
  103.   /**
  104.    *  讀取檔案資料
  105.    * @param handle  檔案控制代碼 
  106.    * @param *buf    儲存資料的快取 
  107.    * @param bytes   讀取的資料長度
  108.    * @returns 返回讀取資料長度   小於0則錯誤
  109.    */
  110. static int Read_File(void* handle, void* buf, int bytes)
  111. {  
  112.   Iap_FlashCont * Filehandle = (Iap_FlashCont *) handle ;
  113.   printf("讀取檔案資料 讀取長度: %ld \r\n",bytes);    
  114.   if(!Filehandle->InitFlat)  //未初始化
  115.   {
  116.     return ERR_MEM ;
  117.   } 
  118.   uint16_t Count ;
  119.   for(  Count = 0 ; (Count < bytes)&&(Filehandle->Flash_Addr<=FLASH_END)  ;  Count++ ,Filehandle->Flash_Addr++ )
  120.   {
  121.    ((uint8_t *)buf)[Count] = *((__IO uint8_t *) Filehandle->Flash_Addr);  
  122.   }
  123.   return Count;
  124. }
  125.  
  126.  
  127.   /**
  128.    *  寫檔案資料
  129.    * @param handle           檔案控制代碼
  130.    * @param struct pbuf* p   資料快取結構體  裡面的資料快取全為實際需要寫入的資料
  131.    * @returns 小於0為錯誤  
  132.    */
  133. static int Write_File(void* handle, struct pbuf* p)
  134. {
  135.   uint16_t Count ;
  136.   Iap_FlashCont * Filehandle = (Iap_FlashCont *) handle ;     
  137.   printf("寫檔案資料  資料長度 %ld \r\n",p->len); 
  138.   if(!Filehandle->InitFlat)
  139.   {
  140.     printf("寫檔案  沒有初始化 \r\n");
  141.     return ERR_MEM ;
  142.   }
  143.   Count = p->len/4 +((p->len%4)>0);  //得到要寫入的資料
  144.   printf("開始寫FLASH  地址 :0X%08X \r\n",Filehandle->Flash_Addr);
  145.   if( FLASH_If_Write((__IO uint32_t*)&Filehandle->Flash_Addr,(uint32_t *)p->payload,Count) == 0 )
  146.   {
  147.     printf("寫FLASH成功  下一次地址 :0X%08X \r\n",Filehandle->Flash_Addr);
  148.     return ERR_OK;
  149.   }
  150.   else
  151.   {
  152.     printf("寫FLASH 失敗 出錯地址 : 0X%08X \r\n",Filehandle->Flash_Addr);
  153.   }
  154.   return ERR_MEM;
  155. }
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
複製程式碼這上面的程式碼我都寫了註釋,理解應該難度不大,就是根據tptp_server.h中的結構體實現對應的輪子函式,分別為:
static void * OpenFile(const char* fname, const char* mode, u8_t write);
static void Close_File(void* handle);
static int Read_File(void* handle, void* buf, int bytes);
static int Write_File(void* handle, struct pbuf* p);


mytftpserverif.h檔案內容如下:

#ifndef     _MYTFTPSERVERIF_H_
#define     _MYTFTPSERVERIF_H_
#include "lwip/mem.h"
#include "lwip/udp.h"
#include "lwip/apps/tftp_server.h"
extern const struct tftp_context  tftpContext;
#endif



因為我們是要通過TFTP 來實現 IAP 功能 ,所有一定有FLASH 的操作,這裡我們之間COPY官方例子的裡面的flash_if.c和flash_if.h,flash_if.c檔案內容如下:
  1. #include "flash_if.h"
  2.  
  3. /* Private typedef -----------------------------------------------------------*/
  4. /* Private define ------------------------------------------------------------*/
  5. /* Private macro -------------------------------------------------------------*/
  6. /* Private variables ---------------------------------------------------------*/
  7. /* Private function prototypes -----------------------------------------------*/
  8.  
  9. /* Private functions ---------------------------------------------------------*/
  10.  
  11. /**
  12.   * @brief  Unlocks Flash for write access
  13.   * @param  None
  14.   * @retval None
  15.   */
  16. void FLASH_If_Init(void)
  17.    HAL_FLASH_Unlock(); 
  18. }
  19.  
  20. /**
  21.   * @brief  This function does an erase of all user flash area
  22.   * @param  StartSector: start of user flash area
  23.   * @retval 0: user flash area successfully erased
  24.   *         1: error occurred
  25.   */
  26. int8_t FLASH_If_Erase(uint32_t StartSector)
  27. {
  28.   uint32_t FlashAddress;
  29.  
  30.   FlashAddress = StartSector;
  31.  
  32.   /* Device voltage range supposed to be [2.7V to 3.6V], the operation will
  33.      be done by word */ 
  34.  
  35.   if (FlashAddress <= (uint32_t) USER_FLASH_LAST_PAGE_ADDRESS)