当前位置:首页 > 单片机 > 单片机
[导读] 1.前言 嵌入式以太网开发是一个很有挑战性的工作。通过几个月的学习,我个人觉得大致有两条途径。第一条途径,先通过高级语言熟悉socket编程,例如C#或C++,对bind,listen,connect,accept等函数熟悉

1.前言

嵌入式以太网开发是一个很有挑战性的工作。通过几个月的学习,我个人觉得大致有两条途径。第一条途径,先通过高级语言熟悉socket编程,例如C#或C++,对bind,listen,connect,accept等函数熟悉之后,应用 lwIP。第二种途径,通过分析嵌入式以太网代码,结合TCPIP协议栈规范逐步实践代码。第一种途径效率高,开发周期短,编写出来的代码性能稳定,第二种途径花的时间长,开发出来的代码功能不完善,但是由于紧紧结合TCPIP规范,可以了解的内容较多,适合学习。本文通过分析和修改AVRNET源码,逐步实现TCPIP协议栈的各个子部分,包括ETHERNET部分,ARP部分,IP部分,ICMP部分,UDP部分,TCP部分和HTTP部分。【STM32NET学习笔记——索引】【代码仓库】

本文将实现UDP部分。

UDP协议全称为用户数据协议,是一种简单有效的运输协议。和以太网首部和IP首部相似,UDP首部也有自身的数据结构定义。从运输协议开始引入端口的概念,端口相当于一个应用程序的标识符。相对于TCP协议而言,UDP协议简单的多。本文将实现UDP协议,并通过几个简单的案例说明UDP的使用。

1.2 相关资料

【ENC28J60学习笔记】

【AVRNET项目(国外)】

【AVR webserver项目(国外)】

1.3 代码仓库

【代码仓库】——CSDN Code代码仓库。

2 UDP部分实现

UDP功能的实现可分为UDP首部填充,UDP缓冲区填充和UDP报文查询。UDP首部填充是一个按部就班的过程,即填充源端口、目标端口、长度和校验和。UDP缓冲区填充即往UDP负载部分逐个填充数据。UDP报文查询功能即匹配本机UDP端口号并进行函数处理。为了实现这些功能,首先需要以下宏定义。需要注意以太网传输协议中数据被以大端的形式保存,即低地址存放了高字节内容。


  1. //UDP默认端口号

  2. #defineUDP_AVR_PORT_V3000

  3. #defineUDP_AVR_PORT_H_V(UDP_AVR_PORT_V>>8)

  4. #defineUDP_AVR_PORT_L_V(UDP_AVR_PORT_V&0xff)

  5. //源端口

  6. #defineUDP_SRC_PORT_H_P0x22

  7. #defineUDP_SRC_PORT_L_P0x23

  8. //目标端口

  9. #defineUDP_DST_PORT_H_P0x24

  10. #defineUDP_DST_PORT_L_P0x25

  11. //UDP负载长度

  12. #defineUDP_LENGTH_H_P0x26

  13. #defineUDP_LENGTH_L_P0x27

  14. //UDP校验和

  15. #defineUDP_CHECKSUM_H_P0x28

  16. #defineUDP_CHECKSUM_L_P0x29

  17. //UDP负载起始地址

  18. #defineUDP_DATA_P0x2A

2.1 UDP首部填充

UDP首部填充中需要明确UDP的端口号,STMNET项目中通过常数宏定义实现。


  1. #defineUDP_AVR_PORT_V3000

  2. #defineUDP_AVR_PORT_H_V(UDP_AVR_PORT_V>>8)

  3. #defineUDP_AVR_PORT_L_V(UDP_AVR_PORT_V&0xff)

从这段代码中可以看出,STMNET的UDP端口号为3000。


  1. voidudp_generate_header(BYTE*rxtx_buffer,WORD_BYTESdest_port,WORD_BYTESlength)

  2. {

  3. WORD_BYTESck;

  4. //默认端口号3000

  5. rxtx_buffer[UDP_SRC_PORT_H_P]=UDP_AVR_PORT_H_V;

  6. rxtx_buffer[UDP_SRC_PORT_L_P]=UDP_AVR_PORT_L_V;

  7. //目标端口地址

  8. rxtx_buffer[UDP_DST_PORT_H_P]=dest_port.byte.high;

  9. rxtx_buffer[UDP_DST_PORT_L_P]=dest_port.byte.low;

  10. //负载长度

  11. rxtx_buffer[UDP_LENGTH_H_P]=length.byte.high;

  12. rxtx_buffer[UDP_LENGTH_L_P]=length.byte.low;

  13. //计算校验和

  14. rxtx_buffer[UDP_CHECKSUM_H_P]=0;

  15. rxtx_buffer[UDP_CHECKSUM_L_P]=0;

  16. //length+8forsource/destinationIPaddresslength(8-bytes)

  17. ck.word=software_checksum((BYTE*)&rxtx_buffer[IP_SRC_IP_P],length.word+8,length.word+IP_PROTO_UDP_V);

  18. rxtx_buffer[UDP_CHECKSUM_H_P]=ck.byte.high;

  19. rxtx_buffer[UDP_CHECKSUM_L_P]=ck.byte.low;

  20. }

2.2 UDP负载长度查询

UDP首部中包含UDP长度描述字节,长度占有两个字节并以大端格式保存,由于宏定义的提示作用,弱化了大端格式的影响。长度中也包括了UDP首部的长度,UDP首部的长度为固定的8字节。


  1. WORDudp_get_dlength(BYTE*rxtx_buffer)

  2. {

  3. WORDlength=0;

  4. //获得UDP长度

  5. length=rxtx_buffer[UDP_LENGTH_H_P]<<8|rxtx_buffer[UDP_LENGTH_L_P];

  6. //去除首部长度

  7. length=length-8;

  8. returnlength;

  9. }

2.3 UDP负载区填充

UDP负载去填充即在UDP首部之后填充有用的数据。在这段真实负载之前包括了UDP首部,IP首部和以太网首部,分别占用了8字节,20字节和14字节。UDP负载的起始地址通过宏由UDP_DATA_P定义。


  1. WORDudp_puts_data(BYTE*rxtx_buffer,BYTE*data,WORDoffset)

  2. {

  3. while(*data)

  4. {

  5. rxtx_buffer[UDP_DATA_P+offset]=*data++;

  6. offset++;

  7. }

  8. returnoffset;

  9. }

2.4 UDP报文查询

UDP报文查询需要匹配接收数据包中的UDP端口号,若匹配成功则可对输入数据包进行处理,这些处理包括解析数据包格式,分析出控制命令或查询命令。也可以通过udp_puts_data向发送缓冲区中填写响应数据。接着逐步生成以太网首部,IP首部和UDP首部,以太网首部中包含目标MAC地址,IP首部中包含目标IP地址,UDP首部中包含目标端口号。

  1. BYTEudp_receive(BYTE*rxtx_buffer,BYTE*dest_mac,BYTE*dest_ip)

  2. {

  3. WORDdlength=0;

  4. //udp负载长度

  5. WORDudp_loadlen=0;

  6. //匹配UDP协议UDP端口号

  7. if(rxtx_buffer[IP_PROTO_P]==IP_PROTO_UDP_V&&rxtx_buffer[UDP_DST_PORT_H_P]==UDP_AVR_PORT_H_V&&rxtx_buffer[UDP_DST_PORT_L_P]==UDP_AVR_PORT_L_V)

  8. {

  9. //获得UDP负载长度

  10. udp_loadlen=udp_get_dlength(rxtx_buffer);

  11. //复制UDP接收

  12. memcpy(udp_recbuf,(char*)&rxtx_buffer[UDP_DATA_P],udp_loadlen);

  13. #ifUDP_DEBUG

  14. printf("UDPMessage!rn");

  15. printf("SendForm:%d.%d.%d.%d",

  16. rxtx_buffer[IP_SRC_IP_P+0],rxtx_buffer[IP_SRC_IP_P+1],

  17. rxtx_buffer[IP_SRC_IP_P+2],rxtx_buffer[IP_SRC_IP_P+3]);

  18. printf("Port:%drn",(rxtx_buffer[UDP_SRC_PORT_H_P]<<8)|rxtx_buffer[UDP_SRC_PORT_L_P]);

  19. printf("Reccive:%srn",udp_recbuf);

  20. #endif

  21. //生成以太网首部

  22. eth_generate_header(rxtx_buffer,(WORD_BYTES){ETH_TYPE_IP_V},dest_mac);

  23. //生成IP首部

  24. ip_generate_header(rxtx_buffer, (WORD_BYTES){sizeof(IP_HEADER)+sizeof(UDP_HEADER)+dlen

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭