分享

TCP 协议在 STM32 上的移植

 心不留意外尘 2016-04-11

from http://blog.csdn.net/xukai871105/article/details/37729201

2014-07-12 
1.前言 
    【由于TCP协议负载,若有说的不对的地方,请及时指出,第一时间改正。本博文2013年2月在某论坛发表,现搬迁至CSDN博客】
    从实用主义的角度出发,学习嵌入式TCPIP可以直接从本章节开始学习,甚至可以直接从HTTP开始学习。我也曾经是一个现实的实用主义者, 以为有了AVRNET项目的源码,修改移植之后便可以用于STM32。但是现实总是那么残酷,对于一个还不熟悉HTML元素,没有任何PHP和SOCKET编程知识的我来说,修改AVRNET的任何一行代码都是不可能完成的任务,我几乎不知道修改这行代码造成哪些变化。但是通过不断地学习情况有所好转,这些学习包括HTML,CSS,JaveScript,PHP,MySQL,SOCKET等。通过这些学习与积累慢慢地揭开了TCPIP的面纱,而通过TCP让STM32返回Hello是多么令人兴奋的事情。
    本文将通过分析和整理AVRNET项目源码并移植到STM32平台中,实现TCP部分内容,通过TCP打印Hello和LED控制实例说明TCP的使用方法。 
    TCP协议是是一个非常复杂的协议,本文并不会实现TCP的方方面面,毫不避讳的说本文所用TCP是不完整的TCP,没有TCP坚持定时器和TCP重发功能,更别提滑动窗口功能,TCP的传输速度也低的可怜。本文试图通过最简单的代码实现最基本的TCP功能,这些包括TCP建立,TCP数据包发送,TCP关闭连接。 
    【代码仓库】——请以代码仓库中的内容为主。

1.2 相关资料
    【AVRNET项目(国外) 】

1.3 学习路线建议
    本文仅仅简单的说明TCP使用的方法,通过一个TCP发射的例子积累学习嵌入式网络应用的信心,但是这些知识是远远不够的,你还需要学习以下知识。这些知识将帮助你设计和实现嵌入式网页,设计优美的嵌入式web类型的API。
    【HTML】先学习HTML4,再学习HTML5
    【CSS】先学习CSS2,再学习CSS3
    【Javascript】
    【JSON】强烈建议学习JSON格式和应用方法。
    【JQuery】建议先熟悉Javascript,再学习JQuery。
    【PHP】和PHP工程师不同,更多关注HTTP部分内容。
    【数据库】学习一款数据库,可以是MySQL或SQLite,熟悉基本操作增删改查。
    【Linux】更多关注应用层设计,如果你不介意尝试一下树莓派吧。
    【RESTFul】学习该框架,了解如何设计HTTP API。
    【等等】知识真的是够多的,请您保持耐心。

1.4 其他说明
    【PC IP】192.168.1.101 
    【STM32 IP】192.168.1.15 
    【TCP端口号】3001

2.TCP协议简述 
    本小节仅介绍TCP如何建立连接,发送数据和终止连接的技巧,本文将不详细讲述TCP首部中的各种字节含义,因为你可以找到很多说明的图书。 更多基础内容请参考。
    【1】《TCP IP详解 卷1》 机械工程出版社出版 
    【2】《嵌入式Internet TCP IP基础、实现及应用》 北京航空航天出版社 

2.1 TCP的建立和终止 
    在STM32NET项目中暂假定PC机为客户端,STM32为服务器端。在这种情况下,有PC机发起连接,STM32被动的处理整个TCP过程。 
【1】PC机试图建立连接,发出SYN标志。 
【2】STM32接收到SYN标志,响应PC机的连接请求,发送SYN和ACK标志 
【3】PC机收到SYN和ACK标志,发送ACK标志,表示TCP连接建立 
【4】PC机在建立连接之后,向STM32发送TCP负载数据,并包含PUSH和ACK标志 
【5】STM32收到负载数据,返回应答数据,并包括FIN、ACK和FUSH标志(FIN意味着AVR试图停止本次TCP连接) 
【6】PC机对负载数据处理,返回ACK标志 
【7】PC机也发出FIN标志和ACK标志,同意停止本次TCP连接 
【8】STM32收到该FIN标志,发出ACK标志,意味着本次TCP通信完全结束。 

    TCP的建立连接,收发数据和停止连接,配合定时器辅以有限状态机是一个常用的实现方式,但实现这些功能需要成千上万行代码,通过分析可以看出。 

    【1】对于FIN标志和SYN标志,无论是服务端还是客户端都需要发送ACK标志 
    【2】STM32共4次收到ACK标志。第一次为上文的第3步,该ACK位的接收表示TCP连接成功,此时负载数据长度为0,STM32对该返回报文不做处理。第二次收到ACK标志,发生在上文的4到5步,和上次收到ACK不同,负载数据一定不为0。第三次收到ACK标志,发生在第6步,此时负载数据长度也为0,STM32对该报文不做任何处理。第四次收到ACK标志发生在第7步,STM32必须应答,但是该报文也含有FIN标志,可以区别于以上几种情况。 
    STM32NET项目便采用以上的方式区别TCP数据包,通过IF ELSE实现了TCP状态机。STM32NET项目先处理SYN标志,接着检查所有的ACK标志,若标志也含有FIN标志,则返回ACK,若负载数据长度为0,不做响应,若负载数据大于0,取出数据做出合适的响应。 

2.2 TCP序号和确认号 
    TCP序号和确认号也是一个比较微妙的细节。总结起来有以下几点。 
    【1】TCP的初始序号由发送SYN或发送FIN时决定。 
    【2】TCP建立连接时,PC机发送SYN时设置初始序号,AVR返回SYN和ACK时设置初始序号,这两个初始序号没有任何逻辑上的关系。 
    【3】确认号为上一个数据包的序号加上个数据包的负载长度。 
    【4】SYN标志和FIN特殊,占用个序号,即确认号需在上个数据包的序号基础累加。 
    STM32NET项目中何时清零序号,何时初始化序号,何时修改确认号,何时修改确认号的确是一个非常麻烦的过程。通过反复的测试,发现即使确认号出现问题程序也能顺利运行。 

3.TCP实现 
    从实用主义角度出发,原理不重要,重要的是如何实现实现功能。和UDP不同,TCP生成首部时的步骤较多。由于TCP首部中存在选项,所以需要通过更多的代码确定TCP负载的位置,在这里可以分为在TCP首部中查找负载长度字节,并通过首部中的TCP偏移字节确定长度 

3.1 TCP数据发送 
    TCP发送时的参数很多,大致可以分为以下步骤。 
    【1】确定下一个确认号的具体值。使用时有两种情况,那么累加1,要么累加上一个报文的负载长度。 
    【2】初始化序号,可以在ABRNET作为客户端时使用 
    【3】清除序号,可以在TCP建立连接时使用 
    【4】设置标志,例如FIN SYN等 
    【5】设置其他参数,例如紧急指针,窗口大小 
    【6】填充以太网首部,IP首部 
    【7】加入校验和 
  1. void tcp_send_packet (  
  2.                       BYTE *rxtx_buffer, //发送缓冲区  
  3.                       WORD_BYTES dest_port, // 目标端口号  
  4.                       WORD_BYTES src_port, // 源端口号  
  5.                       BYTE flags, // TCP标志 FIN SYN ACK  
  6.                       BYTE max_segment_size, // 初始化序号 接收SYN时使用  
  7.                       BYTE clear_seqack, // 设置确认号为0 发送SYN时使用  
  8.                       WORD next_ack_num, // 在上一个数据包序号的基础上累加  
  9.                       WORD dlength, // 负载长度  
  10.                       BYTE *dest_mac, // 目标MAC地址  
  11.                       BYTE *dest_ip ) // 目标IP地址  
  12. {  
  13.     BYTE i, tseq;  
  14.     WORD_BYTES ck;  
  15.      
  16.     // 生成以太网报文头  
  17.     eth_generate_header ( rxtx_buffer, (WORD_BYTES){ETH_TYPE_IP_V}, dest_mac );  
  18.      
  19.     // 计算数据包 确认号 next_ack_num为累加值  
  20.     // 1.确认号因等于上一个数据包的序号加上数据包长度  
  21.     // 2.序号等于上一个数据包的确认号  
  22.     // 3.FIN和SYN各占一个序号  
  23.     // 4.确认号和序号使用大端模式,即高地址存放低位数据  
  24.     // 5.确认号修改发生在三种情况,接收到SYN,接收到FIN,接收到负载数据  
  25.     if ( next_ack_num )  
  26.     {  
  27.         for( i = 4 ; i > 0; i-- )  
  28.         {  
  29.             // 取出上一个数据包的序号,累加next_ack_num  
  30.             next_ack_num = rxtx_buffer [ TCP_SEQ_P + i - 1] + next_ack_num;  
  31.             // 取出上一个数据包的确认号  
  32.             tseq = rxtx_buffer [ TCP_SEQACK_P + i - 1];  
  33.             // 复制本次数据包的确认号,即上个数据包的序号+next_ack_num  
  34.             rxtx_buffer [ TCP_SEQACK_P + i - 1] = 0xff & next_ack_num;  
  35.             // 复制上一个数据包的确认号于本数据包的序号  
  36.             rxtx_buffer[ TCP_SEQ_P + i - 1 ] = tseq;  
  37.              
  38.             next_ack_num >>= 8;  
  39.         }  
  40.     }  
  41.      
  42.     // 初始化序号  
  43.     // 设置最大分片  
  44.     // 第一次发送或接收时使用  
  45.     if ( max_segment_size )  
  46.     {  
  47.         // 初始化序号  
  48.         rxtx_buffer[ TCP_SEQ_P + 0 ] = 0;  
  49.         rxtx_buffer[ TCP_SEQ_P + 1 ] = 0;  
  50.         rxtx_buffer[ TCP_SEQ_P + 2 ] = seqnum;  
  51.         rxtx_buffer[ TCP_SEQ_P + 3 ] = 0;  
  52.         seqnum += 2;  
  53.          
  54.         // 初始化 报文段最大长度  
  55.         rxtx_buffer[ TCP_OPTIONS_P + 0 ] = 2; // 最大报文长度  
  56.         rxtx_buffer[ TCP_OPTIONS_P + 1 ] = 4; // TCP选项长度 TCP选项格式2  
  57.         rxtx_buffer[ TCP_OPTIONS_P + 2 ] = HIGH(1408); //  
  58.         rxtx_buffer[ TCP_OPTIONS_P + 3 ] = LOW(1408); //  
  59.         // 数据偏移,占用高4位,且计算长度为双字  
  60.         rxtx_buffer[ TCP_HEADER_LEN_P ] = 0x60;  
  61.         dlength += 4;  
  62.     }  
  63.     else  
  64.     {  
  65.         // 没有TCP选项时长度为5个双字  
  66.         rxtx_buffer[ TCP_HEADER_LEN_P ] = 0x50;  
  67.     }  
  68.      
  69.     // generate ip header and checksum  
  70.     ip_generate_header ( rxtx_buffer, (WORD_BYTES){(IP_HEADER_LEN + TCP_HEADER_LEN + dlength)}, IP_PROTO_TCP_V, dest_ip );  
  71.      
  72.     // 清除序号,一般使用于发送SYN时  
  73.     if ( clear_seqack )  
  74.     {  
  75.         rxtx_buffer[ TCP_SEQACK_P + 0 ] = 0;  
  76.         rxtx_buffer[ TCP_SEQACK_P + 1 ] = 0;  
  77.         rxtx_buffer[ TCP_SEQACK_P + 2 ] = 0;  
  78.         rxtx_buffer[ TCP_SEQACK_P + 3 ] = 0;  
  79.     }  
  80.      
  81.     // 设置TCP标志  
  82.     rxtx_buffer [ TCP_FLAGS_P ] = flags;  
  83.      
  84.     // 设置目标端口号  
  85.     rxtx_buffer [ TCP_DST_PORT_H_P ] = dest_port.byte.high;  
  86.     rxtx_buffer [ TCP_DST_PORT_L_P ] = dest_port.byte.low;  
  87.      
  88.     // 设置源端口号  
  89.     rxtx_buffer [ TCP_SRC_PORT_H_P ] = src_port.byte.high;  
  90.     rxtx_buffer [ TCP_SRC_PORT_L_P ] = src_port.byte.low;  
  91.      
  92.     // 设置TCP窗口大小  
  93.     rxtx_buffer [ TCP_WINDOWSIZE_H_P ] = HIGH((MAX_RX_BUFFER-IP_HEADER_LEN-ETH_HEADER_LEN));  
  94.     rxtx_buffer [ TCP_WINDOWSIZE_L_P ] = LOW((MAX_RX_BUFFER-IP_HEADER_LEN-ETH_HEADER_LEN));  
  95.      
  96.     // 紧急指针  
  97.     rxtx_buffer[ TCP_URGENT_PTR_H_P ] = 0;  
  98.     rxtx_buffer[ TCP_URGENT_PTR_L_P ] = 0;  
  99.      
  100.     // 计算校验和  
  101.     rxtx_buffer[ TCP_CHECKSUM_H_P ] = 0;  
  102.     rxtx_buffer[ TCP_CHECKSUM_L_P ] = 0;  
  103.   
  104.     ck.word = software_checksum( &rxtx_buffer[IP_SRC_IP_P], TCP_HEADER_LEN+dlength+8, IP_PROTO_TCP_V + TCP_HEADER_LEN + dlength );  
  105.     rxtx_buffer[ TCP_CHECKSUM_H_P ] = ck.byte.high;  
  106.     rxtx_buffer[ TCP_CHECKSUM_L_P ] = ck.byte.low;  
  107.      
  108.     // 通过enc28j60发送数据  
  109.     enc28j60_packet_send ( rxtx_buffer, ETH_HEADER_LEN+IP_HEADER_LEN+TCP_HEADER_LEN+dlength );  
  110. }  

3.2 TCP负载长度查询 
    TCP负载长度查询需要根据IP报文中数据包的总大小和TCP报文中的数据偏移量决定,并需要注意TCP的数据偏移量的单位为双字,即4个字节长度。 
  1. WORD tcp_get_dlength ( BYTE *rxtx_buffer )  
  2. {  
  3.     int dlength, hlength;  
  4.     // 获得IP报文总大小  
  5.     dlength = ( rxtx_buffer[ IP_TOTLEN_H_P ] <<8 ) | ( rxtx_buffer[ IP_TOTLEN_L_P ] );  
  6.     // 除去IP报文头部大小  
  7.     dlength -= IP_HEADER_LEN;  
  8.     // 获得TCP报文数据偏移量 单位为字,需要X4  
  9.     hlength = (rxtx_buffer[ TCP_HEADER_LEN_P ]>>4) * 4;  
  10.     // 除去TCP报文数据偏移量  
  11.     dlength -= hlength;  
  12.      
  13.     if ( dlength <= 0 )  
  14.         dlength=0;  
  15.      
  16.     return ((WORD)dlength);  
  17. }  

3.3 TCP负载位置查询 

  1. BYTE tcp_get_hlength ( BYTE *rxtx_buffer )  
  2. {  
  3.     // 获得TCP报文数据偏移量 单位为字,需要X4  
  4.     return ((rxtx_buffer[ TCP_HEADER_LEN_P ]>>4) * 4); // generate len in bytes;  
  5. }  


3.4 TCP负载填充
    TCP的负载数据填充和UDP的负载数据填充相似。
  1. WORD tcp_puts_data ( BYTE *rxtx_buffer, BYTE *data, WORD offset )  
  2. {  
  3.     while( *data )  
  4.     {  
  5.         rxtx_buffer[ TCP_DATA_P + offset ] = *data++;  
  6.         offset++;  
  7.     }  
  8.      
  9.     return offset;  
  10. }  

3.5 TCP数据包处理 
    TCP数据包的处理代码较多,该函数会返回1或0,1代表以太网接收缓冲区的数据被处理,而0代表数据尚未被处理。TCP数据包的处理包含具体的应用实现,该部分出现在服务器端第二次返回ACK之后,具体的代码请结合范例和上文的TCP连接部分。 
  1. BYTE tcp_receive ( BYTE *rxtx_buffer, BYTE *dest_mac, BYTE *dest_ip )  
  2. {  
  3.     WORD tcp_reclen, tcp_sendlen , dest_port;  
  4.      
  5.     // 获得目标端口号 即客户端端口号  
  6.     dest_port = (rxtx_buffer[TCP_SRC_PORT_H_P]<<8)|rxtx_buffer[TCP_SRC_PORT_L_P];  
  7.     // 匹配TCP协议类型,匹配端口  
  8.     if ( rxtx_buffer [ IP_PROTO_P ] == IP_PROTO_TCP_V && \  
  9.         rxtx_buffer [ TCP_DST_PORT_H_P ] == TCP_AVR_PORT_H_V && \  
  10.             rxtx_buffer [ TCP_DST_PORT_L_P ] == TCP_AVR_PORT_L_V )  
  11.     {  
  12.         // 服务器端第1次发送 收到SYN 返回SYN+ACK  
  13.         if ( (rxtx_buffer[ TCP_FLAGS_P ] & TCP_FLAG_SYN_V) )  
  14.         {  
  15.             tcp_send_packet (  
  16.                              rxtx_buffer, // 发送缓冲区  
  17.                              (WORD_BYTES){dest_port}, // 目标端口号  
  18.                              (WORD_BYTES){TCP_AVR_PORT_V}, // 源端口号  
  19.                              TCP_FLAG_SYN_V|TCP_FLAG_ACK_V, // 标志位 同步位和应答位  
  20.                              1, // 初始化序号,只有在接收到SYN时使用  
  21.                              0, // 不清除确认号  
  22.                              1, // 确认号为上一个数据包的应答编号加1,SYN占用一个序号  
  23.                              0, // 负载长度  
  24.                              dest_mac, // 客户端MAC地址  
  25.                              dest_ip // 客户端IP地址  
  26.                                  );  
  27.              
  28.             return 1;  
  29.         }  
  30.          
  31.         // 收到ACK报文 多种情况  
  32.         if ( (rxtx_buffer [ TCP_FLAGS_P ] & TCP_FLAG_ACK_V) )  
  33.         {  
  34.             // 获得TCP负载长度  
  35.             tcp_reclen = tcp_get_dlength( rxtx_buffer );  
  36.              
  37.             if ( tcp_reclen == 0 )  
  38.             {  
  39.                 // 服务器第4次发送,收到FIN,返回ACK  
  40.                 // 主要是为了区别 建立连接时的最后一个ACK  
  41.                 if ( (rxtx_buffer[TCP_FLAGS_P] & TCP_FLAG_FIN_V) )  
  42.                 {  
  43.                     tcp_send_packet (  
  44.                                      rxtx_buffer, // 发送缓冲区  
  45.                                      (WORD_BYTES){dest_port}, // 目标端口  
  46.                                      (WORD_BYTES){TCP_AVR_PORT_V}, // 源端口  
  47.                                      TCP_FLAG_ACK_V, // 标志位 应答  
  48.                                      0, // 不操作序号  
  49.                                      0, // 不清楚确认号  
  50.                                      1, // FIN占一个序号,在一个数据包的序号的基础上累加1  
  51.                                      0, // 负载大小  
  52.                                      dest_mac, // 客户端MAC地址  
  53.                                      dest_ip // 客户端IP地址  
  54.                                          );  
  55.                 }  
  56.                  
  57.                 return 1;  
  58.             }  
  59.              
  60.             // 服务器第2次发送,返回ACK  
  61.             tcp_send_packet (  
  62.                              rxtx_buffer, // 发送缓冲区  
  63.                              (WORD_BYTES){dest_port}, // 目标端口号  
  64.                              (WORD_BYTES){TCP_AVR_PORT_V}, // 源端口号  
  65.                              TCP_FLAG_ACK_V, // 标志位 应答标志  
  66.                              0, // 不操作序号  
  67.                              0, // 不清除确认号  
  68.                              tcp_reclen, // 在上一个数据包的基础上累加tcp_reclen  
  69.                              0, // 负载长度  
  70.                              dest_mac, // 客户端MAC地址  
  71.                              dest_ip ); // 客户端IP地址  
  72.              
  73.             // 确定TCP负载位置  
  74.             WORD tcp_loadpos = tcp_get_hlength( rxtx_buffer) + ETH_HEADER_LEN + IP_HEADER_LEN;  
  75.             // 复制缓冲区数据  
  76.             memcpy(tcp_recbuf,(char*)&rxtx_buffer[tcp_loadpos],tcp_reclen);  
  77.              
  78.             // 范例1 TCP:Hello  
  79.             //准备返回数据  
  80.             strcpy(tcp_sendbuf,"TCP:Hello ");  
  81.             strcat(tcp_sendbuf,tcp_recbuf);  
  82.             // 填充缓冲区  
  83.             tcp_sendlen = tcp_puts_data( rxtx_buffer,(BYTE*)tcp_sendbuf,0);  
  84.              
  85.             // 服务器第3次发送,发送HTTP响应 发送FIN  
  86.             tcp_send_packet (  
  87.                              rxtx_buffer, // 发送缓冲区  
  88.                              (WORD_BYTES){dest_port}, // 目标端口  
  89.                              (WORD_BYTES){TCP_AVR_PORT_V}, // 源端口  
  90.                              // 标志  
  91.                              TCP_FLAG_ACK_V | TCP_FLAG_PSH_V | TCP_FLAG_FIN_V,  
  92.                              0, // 不操作序号  
  93.                              0, // 不清除确认号  
  94.                              0, //  
  95.                              tcp_sendlen, // 负载长度  
  96.                              dest_mac, // 客户端MAC地址  
  97.                              dest_ip ); // 客户端IP地址  
  98.             // 数据包被处理  
  99.             return 1;  
  100.         }  
  101.     }  
  102.      
  103.     // 返回0 代表数据未被处理  
  104.     return 0;  
  105. }  

4.实验 
    TCP报文的处理位于ARP、 IP、 ICMP和ICMP之后。获得TCP有效负载之后应存在在接收缓冲区中,进行合适的处理并返回结果。实验通过两个例子说明TCP的使用。

4.1 程序结构 

  1. void server_process ( void )  
  2. {  
  3.     MAC_ADDR client_mac;  
  4.     IP_ADDR client_ip;  
  5.     WORD plen;  
  6.      
  7.     // 获得新的IP报文  
  8.     plen = enc28j60_packet_receive( (BYTE*)&rxtx_buffer, MAX_RXTX_BUFFER );  
  9.     if(plen==0) return;  
  10.      
  11.     // 保存客服端的MAC地址  
  12.     memcpy ( (BYTE*)&client_mac, &rxtx_buffer[ ETH_SRC_MAC_P ], sizeof( MAC_ADDR) );  
  13.     // 检查该报文是不是ARP报文  
  14.     if ( arp_packet_is_arp( rxtx_buffer, (WORD_BYTES){ARP_OPCODE_REQUEST_V} ) )  
  15.     {  
  16.         // 向客户端返回ARP报文  
  17.         arp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac );  
  18.         return;  
  19.     }  
  20.      
  21.     // 保存客服端的IP地址  
  22.     memcpy ( (BYTE*)&client_ip, &rxtx_buffer[ IP_SRC_IP_P ], sizeof(IP_ADDR) );  
  23.     // 检查该报文是否为IP报文  
  24.     if ( ip_packet_is_ip ( (BYTE*)&rxtx_buffer ) == 0 )  
  25.     {  
  26.         return;  
  27.     }  
  28.      
  29.     // 如果是ICMP报文 向发起方返回数据  
  30.     if ( icmp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac, (BYTE*)&client_ip ) )  
  31.     {  
  32.         return;  
  33.     }  
  34.      
  35.     // 进行UDP处理  
  36.  if (udp_receive ( (BYTE *)&rxtx_buffer, (BYTE *)&client_mac, (BYTE *)&client_ip ))  
  37.  {  
  38.         return;  
  39.  }  
  40.      
  41.     // 进行TCP处理  
  42.     if (tcp_receive ( (BYTE *)&rxtx_buffer, (BYTE *)&client_mac, (BYTE *)&client_ip ))  
  43.     {  
  44.         return;  
  45.     }  
  46.           
  47. }  

4.2 TCP Hello 
    接收到TCP数据包之后,在负载数据之前加入Hello字符串,如果输入为xukai871105,则返回Hello xukai871105。通过网络调试助手查看返回结果。使用strcpy函数把Hello复制到tcp_sendbuf数组中,接着使用strcat把tcp_recbuf中的字符串连接到tcp_sendbuf之后,最后调用tcp_send_packet填充到发送缓冲区中。
  1. #if TCP_ECHO  
  2.             // 范例1 TCP:Hello  
  3.             //准备返回数据  
  4.             strcpy(tcp_sendbuf,"TCP:Hello ");  
  5.             strcat(tcp_sendbuf,tcp_recbuf);  
  6.             // 填充缓冲区  
  7.             tcp_sendlen = tcp_puts_data(rxtx_buffer,(BYTE*)tcp_sendbuf,0);  
  8. #endif  
图1 TCP Hello实验结果 
4.3 LED控制 
    验证TCP发送和接收之后,可以通过定义一组指令实现LED的控制。
    led,x,y
    x表示LED编号,取值范围为1或2
    y表示LED状态,1为打开,2为关闭

    具体代码如下:
  1. #if TCP_LEDCTRL  
  2.             int match_count = 0;  
  3.             int led_index = 0;  
  4.             int led_status = 0;  
  5.             // 匹配led,x,y  
  6.             match_count = sscanf(tcp_recbuf,"led,%d,%d", &led_index, &led_status);  
  7.             if(match_count == 2)  
  8.             {  
  9.                 switch(led_index)  
  10.                 {  
  11.                 case 1:  
  12.                     led_status?BSP_LEDOn(1):BSP_LEDOff(1);  
  13.                     tcp_sendlen = tcp_puts_data(rxtx_buffer, (BYTE*)"LED1 Control OK\r\n", 0);  
  14.                     // dlength = udp_puts_data(rxtx_buffer, (BYTE*)"LED1 Control OK\r\n", 0);  
  15.                     break;  
  16.                 case 2:  
  17.                     led_status?BSP_LEDOn(2):BSP_LEDOff(2);  
  18.                     tcp_sendlen = tcp_puts_data(rxtx_buffer, (BYTE*)"LED2 Control OK\r\n", 0);  
  19.                     break;  
  20.                 default:  
  21.                     tcp_sendlen = tcp_puts_data(rxtx_buffer, (BYTE*)"Invalid LED Index\r\n", 0);  
  22.                     break;  
  23.                 }  
  24.             }  
  25.             else  
  26.             {  
  27.                 tcp_sendlen = tcp_puts_data(rxtx_buffer, (BYTE*)"unknow command\r\n", 0);  
  28.             }  
  29. #endif  

图2 TCP LED控制

5 总结和展望 
    本文为STM32NET学习笔记的最后一篇,以后将不再更新。按照最原始的计划还有WEB部分的内容,但是若使用这样的框架尝试,那么整个系统会变得非常复杂且不可控制。如果想深入嵌入式网络,请查阅LwIP相关知识,或者尝试一下树莓派等linux开发板。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多