通信
ETH
LWIP
MX_LWIP_Init()
初始化LwIP的内存管理和各个协议层。
按顺序执行了:
网络接口的添加 netif_add()
初始化底层 ethernetif_init()
DHCP dhcp_start()
然后LwIP就可以用了。
收包用的是调用 low_level_input 把数据包接回来,给 netif->input 处理。
发包则是由 netif->output 交由 etharp_output 制作数据包,调用 low_level_output 发出去。
MX_LWIP_Init 初始化网卡 (IP地址、子网掩码、网关IP)
MX_LWIP_Init 调用netif_add函数,添加带RTOS的网络接口(IPv4/IPv6)
netif_add函数会调用ethernetif_init,ethernetif_init内将调用low_level_init(netif);
low_level_init内会进行eth的初始化(通过初始化heth句柄,与调用HAL_ETH_Init)
HAL_ETH_Init 会调用 HAL_ETH_MspInit (初始化ETH相关的时钟、GPIO与中断)
MX_LWIP_Process()
不断地接收来自接口的信息,并检查是否延时。
1.网卡抽象层
为了于底层硬件网络接口衔接,LwIP作为轻量级的TCP/IP,为了兼容各种物理层芯片和底层硬件,其对网卡进行了抽象,网卡的抽象层都在netif.c和netif.h中,其中涵盖了许多关于网卡的操作,如网卡的注册和删除、启用与禁用、网卡IP设置等等。
在网卡初始化过程中,通过调用网卡抽象层的函数主要有:
1.netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);
其中,gnetif是LwIP中定义的网卡抽象的结构体,HAL库在网卡初始化之前先往gnetif结构体填充好数据。ipaddr、netmask和gw分别设置为我们所需的stm32的静态IP、子网掩码和网关,这里我们设置成与雷达IP的同一网段,前三个地址字节是一样的,即可。
2.netif_set_default(&gnetif);
在经过步骤1的网卡抽象结构体填充后,通过步骤2来进行网卡注册,将网卡注册到网卡链表当中。
3. if (netif_is_link_up(&gnetif))
{/* When the netif is fully configured this function must be called */
netif_set_up(&gnetif);
}
else
{/* When the netif link is down this function must be called */
netif_set_down(&gnetif);
}
接着调用netif_is_link_up(&gnetif)检查netif是否配置好,当配置好之后,一切就绪,调用netif_set_up(&gnetif);启动网卡,可以开始数据传输了。
2.网卡驱动层
ethernetif.c 文件是无操作系统时网络接口函数,该文件在移植是只需修改相关头文件名,函数实现部分无需修改。该文件主要有三个部分函数,
一个是 low_level_init,用于初始化 MAC 相关工作环境、初始化 DMA 描述符链表,并使能 MAC 和 DMA;
一个是 low_level_output,它是最底层发送一帧数据函数;
最后一个是 low_level_input,它是最底层接收一帧数据函数。
网卡驱动层作为底层接口,为网卡抽象层提供注册操作,给每个netif接口提供访问硬件的支持。在实际开发移植中,我们往往需要根据自己实际网卡特性去完善修改底层驱动的函数即可。与网卡驱动密切相关的函数有3个,分别是low_level_init()、low_level_output()和low_level_input()。
1.low_level_init()为网卡初始化函数,主要完成网卡的复位和参数初始化,根据实际的网卡属性配置netif中与网卡相关的字段,如网卡的MAC地址、长度、最大发送单元等等
2.low_level_output()为网卡的发送函数,主要将内核的数据包发送出去,数据包的封装采用pbuf数据结构进行描述,而pbuf数据结构专门用了pbuf.c和pbuf.h进行定义。
3.low_level_input()是网卡的数据接收函数,同样将接收到的数据采取pbuf的形式进行各层之间的递交,保持收发的一致性且方便LwIP内核处理。
pbuf
/** Main packet buffer struct */
struct pbuf {
/** next pbuf in singly linked pbuf chain */
struct pbuf *next;
/** pointer to the actual data in the buffer */
void *payload;
/**
* total length of this buffer and all next buffers in chain
* belonging to the same packet.
*
* For non-queue packet chains this is the invariant:
* p->tot_len == p->len + (p->next? p->next->tot_len: 0)
*/
u16_t tot_len;
/** length of this buffer */
u16_t len;
/** a bit field indicating pbuf type and allocation sources
(see PBUF_TYPE_FLAG_*, PBUF_ALLOC_FLAG_* and PBUF_TYPE_ALLOC_SRC_MASK)
*/
u8_t type_internal;
/** misc flags */
u8_t flags;
/**
* the reference count always equals the number of pointers
* that refer to this pbuf. This can be pointers from an application,
* the stack itself, or pbuf->next pointers from a chain.
*/
LWIP_PBUF_REF_T ref;
/** For incoming packets, this contains the input netif's index */
u8_t if_idx;
};
LwIP库使用struct pbuf
结构体来表示数据包。
以下是各参数的详细说明:
-
payload
参数:
这是一个指向数据缓冲区的指针,表示数据包的有效负载(payload)。 -
len
参数:
这是一个u16_t
类型的参数,表示数据包的有效负载长度。 -
tot_len
参数:
这是一个u16_t
类型的参数,表示整个数据包的总长度。 -
next
参数:
这是一个指向下一个struct pbuf
结构体的指针,用于连接多个数据包。 -
type
参数:
这是一个枚举类型(enum pbuf_type
),表示数据包的类型,例如PBUF_POOL或PBUF_RAM。
使用场景:struct pbuf
结构体适用于在STM32上处理数据包的场景,例如网络通信中的接收和发送数据。
使用方法:
以下是使用struct pbuf
结构体的一般过程:
- 创建一个
struct pbuf
结构体对象。 - 设置有效负载数据和长度。
- 可选:连接多个数据包。
- 处理数据包。
下面是一个简单的示例,演示如何使用struct pbuf
结构体:
struct pbuf *p, *p2;
// 创建一个pbuf对象
p = pbuf_alloc(PBUF_RAW, payload_len, PBUF_POOL);
if(p == NULL) {
// 创建失败
// 处理错误...
}
// 设置有效负载数据和长度
memcpy(p->payload, payload_data, payload_len);
// 连接多个数据包(可选)
p2 = pbuf_alloc(PBUF_RAW, payload_len2, PBUF_POOL);
p->tot_len = p->len + p2->len;
pbuf_cat(p, p2);
p2 = NULL;
// 处理数据包
// ...
// 释放pbuf对象
pbuf_free(p);
p = NULL;
在上面的示例中:
首先,我们使用pbuf_alloc()
函数创建一个新的struct pbuf
结构体对象p
。我们指定了数据包的类型(PBUF_RAW),有效负载长度(payload_len)和内存分配类型(PBUF_POOL)。如果创建失败,需要根据情况处理错误。
然后,我们可以使用p->payload
指针来访问有效负载数据,并使用p->len
设置有效负载长度。注意,有效负载数据可以使用memcpy()
等函数进行复制。
接下来,我们可以通过调用pbuf_alloc()
创建另一个struct pbuf
结构体对象p2
,以支持多个数据包连接。然后,我们通过设置p->tot_len
和调用pbuf_cat()
函数将p2
连接到p
的末尾。请注意,连接后需要将p2
设置为NULL。
最后,我们可以在处理数据包的逻辑中使用pbuf
对象。
请注意,在不再需要使用pbuf
对象时,应使用pbuf_free()
函数将其释放,并将其设置为NULL。
总结一下,struct pbuf
结构体用于在STM32上表示和处理数据包。您可以通过设置有效负载数据和长度,以及连接多个数据包,来创建和操作pbuf
对象。然后,您可以在网络通信中使用pbuf
对象来接收和发送数据。
lwipopts.h 文件存放一些宏定义,用于剪切 LwIP 功能,比如有无操作系统、内存空间分配、存储池分配、 TCP 功能、 DHCP 功能、 UDP 功能选择等等。
CANopen
SDO_SVR
表示SDO server的索引;
SDO_CLT
表示SDO client的索引;
PDO_RCV
表示RPDO的索引;
PDO_RCV_MAP
表示RPDO映射对象的索引;
PDO_TRS
表示TPDO的索引;
PDO_TRS_MAP
表示TPDO映射对象的索引。
使用的读写函数:
写操作 主要用到
UNS8 writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex,
UNS32 count, UNS8 dataType, void *data, UNS8 useBlockMode);
和获取结果的函数
UNS8 getWriteResultNetworkDict (CO_Data* d, UNS8 nodeId, UNS32 * abortCode)
读操作 主要用到
UNS8 readNetworkDictCallback (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS8 dataType, SDOCallback_t Callback, UNS8 useBlockMode)
和获取结果的函数
UNS8 getReadResultNetworkDict (CO_Data* d, UNS8 nodeId, void* data, UNS32 *size,
UNS32 * abortCode)
相关函数
LwIP
tcp_write()
函数位置:tcp_out.c
tcp_write()
函数用于将数据写入TCP连接的发送缓冲区。它可以进行通过TCP网络连接发送数据的操作。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要进行数据发送的TCP连接。 -
data
参数:
这是一个指向要发送数据的缓冲区的指针。 -
len
参数:
这是要发送的数据的长度。 -
flags
参数:
这是一个用于控制发送行为的标志,常用的标志有:TCP_WRITE_FLAG_COPY
:将数据复制到内部发送缓冲区。TCP_WRITE_FLAG_MORE
:表示后续的tcp_write()
调用将紧随在当前发送之后。
-
返回值
:
该函数返回已成功发送的数据长度。
使用场景:tcp_write()
函数适用于需要通过TCP连接发送数据的场景,例如TCP客户端与TCP服务器之间的数据通信。
使用方法:
以下是使用tcp_write()
函数的一般过程:
- 使用
tcp_connect()
建立TCP连接,或者等待TCP服务器的连接请求。 - 创建一个发送缓冲区。
- 使用
tcp_write()
函数将要发送的数据写入发送缓冲区。 - 使用
tcp_output()
函数发送数据。
下面是一个简单的示例,演示如何使用tcp_write()
函数发送数据:
struct tcp_pcb *pcb; // TCP连接
char sendData[20] = "Hello, Server!"; // 要发送的数据
uint16_t dataLen = strlen(sendData); // 要发送的数据长度
// 创建TCP连接,并建立连接
// 将要发送的数据写入发送缓冲区
if (tcp_write(pcb, sendData, dataLen, TCP_WRITE_FLAG_COPY) == ERR_OK) {
// 数据成功写入发送缓冲区,请进行相应的处理
// ...
} else {
// 数据写入发送缓冲区失败,请进行相应的处理
// ...
}
// 发送数据
tcp_output(pcb);
在上面的示例中,我们定义了一个指向struct tcp_pcb
的指针pcb
,表示我们要进行数据发送的TCP连接。然后,我们创建了一个要发送的数据缓冲区sendData
,并计算出数据的长度dataLen
。
接下来,我们调用tcp_write()
函数,传递pcb
指针、要发送的数据的缓冲区指针sendData
、数据长度dataLen
和标志TCP_WRITE_FLAG_COPY
作为参数,将数据写入TCP连接的发送缓冲区。如果数据成功写入发送缓冲区,那么我们可以进行相应的处理;如果写入失败,我们也可以进行相应的处理。
最后,我们使用tcp_output()
函数将数据发送出去。
tcp_recv()
tcp_recv()
函数的定义和作用与TCP/IP协议栈的接收数据处理密切相关。该函数用于接收和处理TCP数据包。
函数的定义如下:
err_t tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn callback);
参数说明:
pcb
:指向TCP控制块(PCB)的指针,该控制块包含了TCP连接的相关信息。callback
:指向接收数据处理回调函数的指针。当接收到数据时,该回调函数将被调用以处理接收到的数据。
函数的作用:
tcp_recv()
函数用于接收和处理TCP数据包。当TCP/IP协议栈接收到数据时,它会调用指定的回调函数来处理接收到的数据。通过使用tcp_recv()
函数,您可以注册一个回调函数来处理接收到的数据,并在回调函数中进行相应的数据处理。
使用场景:
- 当您需要在STM32上实现TCP/IP通信时,可以使用
tcp_recv()
函数来接收和处理TCP数据包。 - 适用于需要进行TCP连接、接收数据等应用的开发。
使用方法示例:
假设您已经成功建立了一个TCP连接,并使用tcp_recv()
函数注册了一个接收数据处理回调函数。当接收到数据时,指定的回调函数将被调用以处理接收到的数据。以下是一个简单的示例代码:
// 假设已经成功建立了TCP连接,并获得了指向pcb的指针
struct tcp_pcb *pcb = ...;
// 定义接收数据处理回调函数
void recv_callback(struct tcp_pcb *pcb, void *arg, const void *data, u16_t len) {
// 在这里处理接收到的数据
// ...
}
// 调用tcp_recv()函数注册接收数据处理回调函数
tcp_recv(pcb, recv_callback);
在上面的示例中,我们定义了一个名为recv_callback()
的回调函数来处理接收到的数据。然后,我们使用tcp_recv()
函数将该回调函数注册为接收数据处理回调函数。当接收到数据时,协议栈将调用该回调函数并传递相关的参数(如PCB指针、接收到的数据等)。在回调函数中,您可以根据需要处理接收到的数据。
tcp_recved()
函数位置:tcp.c
tcp_recved()
函数用于通知TCP协议栈已经接收了指定数量的数据。它通过更新TCP连接的接收窗口来反映已接收数据的数量。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要更新接收窗口的TCP连接。 -
len
参数:
这是表示已接收数据数量的整数值。
使用场景:tcp_recved()
函数适用于需要通知TCP协议栈已接收数据的情况,例如在接收数据后更新接收窗口,使对方知道已接收数据的数量。
使用方法:
以下是使用tcp_recved()
函数的一般过程:
- 建立TCP连接,等待数据到达。
- 在接收到数据后,使用
tcp_recved()
函数通知TCP协议栈已接收数据。
下面是一个简单的示例,演示如何使用tcp_recved()
函数:
struct tcp_pcb *pcb; // TCP连接
uint16_t receivedDataLen; // 已接收的数据长度
// 创建TCP连接,并建立连接
// 在接收到数据后,使用tcp_recved()通知TCP协议栈已接收数据
tcp_recved(pcb, receivedDataLen);
在上面的示例中,我们定义了一个指向struct tcp_pcb
的指针pcb
,表示要更新接收窗口的TCP连接。然后,我们定义了一个变量receivedDataLen
,表示已接收的数据的长度。
当接收到数据后,我们调用tcp_recved()
函数,传递pcb
指针和已接收数据的长度receivedDataLen
作为参数,通知TCP协议栈已接收数据。这将更新接收窗口的状态,使对方知道已接收的数据数量。
IP4_ADDR()
函数位置:ip4_addr.h
IP4_ADDR()
是一个宏定义,用于将一个IPv4地址分解为四个字节。
以下是各参数的详细说明:
-
ipaddr
参数:
这是一个ip4_addr_t
类型的指针,表示要设置的IPv4地址。 -
a
,b
,c
,d
参数:
这是四个表示IPv4地址的无符号8位整数,范围为0~255。
使用场景:IP4_ADDR()
宏适用于需要将一个IP地址分解为四个字节的场景,例如在网络编程中需要将IP地址转换为字节流。
使用方法:
以下是使用IP4_ADDR()
宏的一般过程:
- 创建一个
ip4_addr_t
类型的变量。 - 使用
IP4_ADDR()
宏将IPv4地址分解为四个字节,并赋值给变量。
下面是一个简单的示例,演示如何使用IP4_ADDR()
宏将IPv4地址分解为四个字节:
ip4_addr_t ip_addr; // IPv4地址保存变量
// 将IPv4地址分解为四个字节,并赋值给变量
IP4_ADDR(&ip_addr, 192, 168, 0, 1);
在上面的示例中,我们创建了一个ip4_addr_t
类型的变量ip_addr
,用于保存IPv4地址。
然后,我们使用IP4_ADDR()
宏,传递&ip_addr
作为第一个参数,以及IPv4地址的四个字节作为后续的参数(192、168、0、1),将IPv4地址分解为四个字节,并将结果赋值给变量ip_addr
。
tcp_poll()
函数位置:tcp.c
tcp_poll()
函数用于在TCP连接中设置一个poll事件,定期触发回调函数以执行特定的操作。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要设置poll事件的TCP连接。 -
poll_interval
参数:
这是表示poll事件触发的时间间隔,以系统时钟(通常是以毫秒为单位)为单位。 -
poll_handler
参数:
这是一个指向回调函数的指针,当poll事件触发时,将调用该回调函数执行特定的操作。
使用场景:tcp_poll()
函数适用于需要定期触发回调函数执行特定操作的场景,例如定期检查某个状态或执行某些操作,如心跳包的发送。
使用方法:
以下是使用tcp_poll()
函数的一般过程:
- 建立TCP连接,等待进行数据传输。
- 在需要设置poll事件的时候,调用
tcp_poll()
函数进行设置。
下面是一个简单的示例,演示如何使用tcp_poll()
函数:
struct tcp_pcb *pcb; // TCP连接
// 定义回调函数
err_t poll_handler(void *arg, struct tcp_pcb *tpcb) {
// 在这里执行特定的操作
// ...
// 返回ERR_OK表示操作成功
return ERR_OK;
}
// 在建立TCP连接后,设置poll事件
tcp_poll(pcb, poll_handler, poll_interval);
在上面的示例中,我们首先定义了一个指向struct tcp_pcb
的指针pcb
,表示要设置poll事件的TCP连接。
然后,我们定义了一个名为poll_handler
的回调函数,该函数在poll事件触发时被调用,用于执行特定的操作。
最后,我们调用tcp_poll()
函数,传递pcb
指针、poll_handler
回调函数的指针以及poll_interval
作为参数,进行poll事件的设置。
tcp_bind()
tcp_bind()
函数用于将一个TCP连接绑定到一个指定的本地地址和端口。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要绑定的TCP连接。 -
ipaddr
参数:
这是一个指向ip_addr_t
结构体类型的指针,表示要绑定的本地IP地址。 -
port
参数:
这是一个表示要绑定的本地端口号的无符号16位整数。
使用场景:tcp_bind()
函数适用于需要将TCP连接绑定到特定的本地地址和端口的场景,例如在TCP服务器中的监听套接字上进行地址绑定操作。
使用方法:
以下是使用tcp_bind()
函数的一般过程:
- 建立一个TCP连接。
- 调用
tcp_bind()
函数将连接绑定到指定的本地地址和端口。
下面是一个简单的示例,演示如何使用tcp_bind()
函数:
struct tcp_pcb *pcb; // TCP连接指针
ip_addr_t local_ip; // 本地IP地址
IP4_ADDR(&local_ip, 192, 168, 0, 1);
uint16_t local_port = 1234; // 本地端口号
// 建立TCP连接...
pcb = tcp_new();
// 将连接绑定到指定的本地地址和端口
tcp_bind(pcb, &local_ip, local_port);
在上面的示例中,首先定义了一个指向struct tcp_pcb
的指针pcb
,表示要绑定的TCP连接。
然后,我们定义了一个ip_addr_t
类型的变量local_ip
,用于表示要绑定的本地IP地址。在示例中,我们将IPv4地址192.168.0.1分配给该变量。
接下来,我们定义了一个无符号16位整数local_port
,表示要绑定的本地端口号。在示例中,我们将端口号设置为1234。
最后,我们调用tcp_bind()
函数,传递pcb
指针、local_ip
指针和local_port
作为参数,将TCP连接绑定到指定的本地地址和端口。
tcp_listen()
tcp_listen()
函数用于在TCP服务器中创建一个监听套接字,并开始监听客户端的连接请求。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要创建的监听套接字。 -
backlog
参数:
这是一个表示可以排队等待连接的客户端连接请求数量的无符号8位整数。 -
listen_handler
参数:
这是一个指向回调函数的指针,当有新的连接请求时,将调用该回调函数进行处理。
使用场景:tcp_listen()
函数适用于需要在TCP服务器中创建一个监听套接字并监听来自客户端的连接请求的场景。
使用方法:
以下是使用tcp_listen()
函数的一般过程:
- 建立一个TCP连接。
- 调用
tcp_new()
函数创建一个新的TCP连接。 - 调用
tcp_bind()
函数将新的TCP连接绑定到指定的本地地址和端口。 - 调用
tcp_listen()
函数开始监听客户端的连接请求。 - 当有新的连接请求时,调用
listen_handler
回调函数进行处理。
下面是一个简单的示例,演示如何使用tcp_listen()
函数:
struct tcp_pcb *listener_pcb; // 监听套接字指针
ip_addr_t local_ip; // 本地IP地址
IP4_ADDR(&local_ip, 192,168,1,100);
uint16_t local_port = 1234; // 本地端口号
uint8_t backlog = 5; // 最大等待连接数量
// 建立TCP连接...
listener_pcb = tcp_new();
tcp_bind(listener_pcb, &local_ip, local_port);
// 开始监听客户端连接请求
tcp_listen(listener_pcb, backlog, listen_handler);
// 处理连接请求的回调函数
err_t listen_handler(void *arg, struct tcp_pcb *newpcb, err_t err) {
// 在这里处理连接请求
// ...
// 返回ERR_OK表示处理成功
return ERR_OK;
}
在上面的示例中,首先定义了一个指向struct tcp_pcb
的指针listener_pcb
,表示要创建的监听套接字。
然后,我们定义了一个ip_addr_t
类型的变量local_ip
,用于表示要绑定的本地IP地址。在示例中,我们将IPv4地址192.168.1.100分配给该变量。
接下来,我们定义了一个无符号16位整数local_port
,表示要绑定的本地端口号。在示例中,我们将端口号设置为1234。
然后,我们定义了一个无符号8位整数backlog
,表示可以排队等待连接的最大客户端连接请求数量。在示例中,我们设置最大等待连接数量为5。
接下来,我们调用tcp_new()
函数和tcp_bind()
函数,创建一个新的TCP连接并将其绑定到指定的本地地址和端口。
然后,我们调用tcp_listen()
函数,传递listener_pcb
指针、backlog
作为参数,并指定一个回调函数listen_handler
用于处理客户端连接请求。
最后,我们定义了listen_handler
回调函数,用于处理客户端连接请求。在实际应用中,该函数应该根据具体的业务逻辑进行相应的适配和配置。
tcp_accept()
tcp_accept()
函数用于接受来自客户端的连接请求,创建一个新的套接字用于与客户端通信。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示正在监听的套接字。 -
accept_handler
参数:
这是一个指向回调函数的指针,当有新的连接请求时,将调用该回调函数进行处理。
使用场景:tcp_accept()
函数适用于TCP服务器接受客户端连接请求,并创建一个新的套接字用于与客户端通信的场景。
使用方法:
以下是使用tcp_accept()
函数的一般过程:
- 建立一个TCP连接。
- 调用
tcp_new()
函数创建一个新的TCP连接。 - 调用
tcp_bind()
函数将新的TCP连接绑定到指定的本地地址和端口。 - 调用
tcp_listen()
函数开始监听客户端的连接请求。 - 当有新的连接请求时,调用
accept_handler
回调函数进行处理。 - 在
accept_handler
回调函数中,调用tcp_accept()
函数接受连接请求,并创建一个新的套接字用于与客户端通信。 - 在新的套接字上进行与客户端的通信。
下面是一个简单的示例,演示如何使用tcp_accept()
函数:
struct tcp_pcb *listener_pcb; // 监听套接字指针
ip_addr_t local_ip; // 本地IP地址
IP4_ADDR(&local_ip, 192,168,1,100);
uint16_t local_port = 1234; // 本地端口号
// 建立TCP连接...
listener_pcb = tcp_new();
tcp_bind(listener_pcb, &local_ip, local_port);
// 开始监听客户端连接请求
tcp_listen(listener_pcb, 5, listen_handler);
// 处理连接请求的回调函数
err_t listen_handler(void *arg, struct tcp_pcb *newpcb, err_t err) {
// 在这里处理连接请求
// ...
// 调用tcp_accept函数接受连接请求,并创建新的套接字
struct tcp_pcb *new_sock_pcb = tcp_accept(listener_pcb, accept_handler);
// 在新的套接字上进行与客户端的通信
if (new_sock_pcb != NULL) {
// 进行通信...
}
// 返回ERR_OK表示处理成功
return ERR_OK;
}
// 处理新连接的回调函数
err_t accept_handler(void *arg, struct tcp_pcb *newpcb, err_t err) {
// 在这里处理新的连接
// ...
// 返回ERR_OK表示处理成功
return ERR_OK;
}
在上面的示例中,首先定义了一个指向struct tcp_pcb
的指针listener_pcb
,表示正在监听的套接字。
然后,我们定义了一个ip_addr_t
类型的变量local_ip
,用于表示要绑定的本地IP地址。在示例中,我们将IPv4地址192.168.1.100分配给该变量。
接下来,我们定义了一个无符号16位整数local_port
,表示要绑定的本地端口号。在示例中,我们将端口号设置为1234。
然后,我们调用tcp_new()
函数和tcp_bind()
函数,创建一个新的TCP连接并将其绑定到指定的本地地址和端口。
接下来,我们调用tcp_listen()
函数,传递listener_pcb
指针和5作为参数,并指定一个回调函数listen_handler
用于处理客户端连接请求。
在listen_handler
回调函数中,我们可以使用tcp_accept()
函数接受连接请求,并创建一个新的套接字用于与客户端通信。如果tcp_accept()
函数执行成功,它将返回一个指向新套接字的指针。
tcp_sent()
tcp_sent()
函数的定义和作用与TCP/IP协议栈的实现密切相关。该函数用于通知TCP/IP协议栈,某个TCP数据包已经被成功发送到网络上。
函数的定义如下:
void tcp_sent(struct tcp_pcb *pcb, u16_t len);
参数说明:
pcb
:指向TCP控制块(PCB)的指针,该控制块包含了TCP连接的相关信息。len
:表示已发送的数据包长度(字节数)。
函数的作用:
tcp_sent()
函数用于通知TCP/IP协议栈,一个TCP数据包已经被成功发送到网络上。当应用程序调用底层网络接口(如UART、SPI等)发送数据时,一旦数据成功发送,就需要调用tcp_sent()
函数来通知TCP/IP协议栈。这样,协议栈可以更新连接状态、释放用于发送的数据缓冲区等。
使用场景:
- 当应用程序使用STM32的TCP/IP协议栈时,需要在发送TCP数据包后调用
tcp_sent()
函数来通知协议栈。 - 适用于需要进行TCP连接、数据传输等应用的开发。
使用方法示例:
假设你已经成功建立了一个TCP连接,并使用底层网络接口发送了数据。一旦数据成功发送,你可以调用tcp_sent()
函数来通知TCP/IP协议栈。以下是一个简单的示例代码:
// 假设已经成功建立了TCP连接,并获得了指向pcb的指针
struct tcp_pcb *pcb = ...;
// 假设已经成功发送了一个TCP数据包,并获得了已发送的数据包长度len
u16_t len = ...;
// 调用tcp_sent()函数通知TCP/IP协议栈
tcp_sent(pcb, len);
请注意,具体的实现可能因STM32的硬件平台和TCP/IP协议栈的不同而有所差异。在实际应用中,请参考相关的文档和示例代码来进行开发和调试。
tcp_close()
tcp_close()
函数用于关闭一个TCP连接,并释放相关资源。
以下是各参数的详细说明:
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要关闭的TCP连接。
使用场景:tcp_close()
函数适用于在STM32上关闭一个已建立的TCP连接的场景。
使用方法:
以下是使用tcp_close()
函数的一般过程:
- 建立一个TCP连接。
- 当需要关闭连接时,调用
tcp_close()
函数。
下面是一个简单的示例,演示如何使用tcp_close()
函数:
struct tcp_pcb *pcb; // 套接字指针
// 建立TCP连接...
pcb = tcp_new();
// ...
// 需要关闭连接时
tcp_close(pcb);
在上面的示例中,首先定义了一个指向struct tcp_pcb
的指针pcb
,表示要关闭的TCP连接。
然后,我们建立了一个TCP连接的过程(在示例中省略了具体的代码)。
最后,当需要关闭连接时,我们调用tcp_close()
函数,并传递pcb
作为参数。
请注意,关闭TCP连接后,需要确保释放与连接相关的资源,如内存、套接字等。
tcp_connect()
tcp_connect()
函数用于建立一个TCP连接。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要建立的TCP连接。 -
remote_ip
参数:
这是一个ip_addr_t
类型的指针,表示远程(目标)IP地址。 -
port
参数:
这是一个u16_t
类型的参数,表示远程(目标)端口号。
使用场景:tcp_connect()
函数适用于在STM32上建立一个TCP连接的场景。
使用方法:
以下是使用tcp_connect()
函数的一般过程:
- 创建一个TCP控制块(pcb)。
- 设置pcb的连接回调函数(可选)。
- 调用
tcp_connect()
函数,传递pcb、远程IP和端口参数。
下面是一个简单的示例,演示如何使用tcp_connect()
函数:
struct tcp_pcb *pcb; // 套接字指针
ip_addr_t remote_ip;
u16_t port;
// 创建TCP控制块
pcb = tcp_new();
if(pcb == NULL) {
// 创建失败
// 处理错误...
}
// 设置连接回调函数(可选)
tcp_arg(pcb, NULL); // 可以传递一个自定义参数
// 设置远程IP和端口
IP4_ADDR(&remote_ip, 192, 168, 1, 100); // 设置远程IP地址
port = 1234; // 设置远程端口号
// 建立连接
err_t err = tcp_connect(pcb, &remote_ip, port, tcp_connect_callback);
if(err != ERR_OK) {
// 连接失败
// 处理错误...
}
在上面的示例中:
首先,我们创建了一个struct tcp_pcb
的指针pcb
,表示要建立的TCP连接。
然后,我们调用tcp_new()
函数来创建一个新的TCP控制块(pcb)。如果创建失败,需要根据情况处理错误。
接下来,我们可以设置一个连接回调函数(可选),通过tcp_arg()
函数传递自定义参数。
然后,我们设置远程IP地址和端口号,这里使用了IP4_ADDR()
宏来设置IP地址。
最后,我们调用tcp_connect()
函数,传递pcb
、远程IP和端口参数以及一个连接回调函数(在示例中命名为tcp_connect_callback
)。
tcp_arg()
tcp_arg()
函数用于将一个自定义参数与TCP控制块(pcb)关联起来。
以下是各参数的详细说明:
-
pcb
参数:
这是一个指向struct tcp_pcb
结构体类型的指针,表示要关联自定义参数的TCP控制块。 -
arg
参数:
这是一个任意类型的指针,表示要关联的自定义参数。
使用场景:tcp_arg()
函数适用于在STM32上将自定义参数与TCP控制块关联的场景。
使用方法:
以下是使用tcp_arg()
函数的一般过程:
- 创建一个TCP控制块(pcb)。
- 调用
tcp_arg()
函数,传递pcb和自定义参数。
下面是一个简单的示例,演示如何使用tcp_arg()
函数:
struct tcp_pcb *pcb; // 套接字指针
void *my_arg; // 自定义参数指针
// 创建TCP控制块
pcb = tcp_new();
if(pcb == NULL) {
// 创建失败
// 处理错误...
}
// 关联自定义参数
my_arg = ...; // 初始化自定义参数
tcp_arg(pcb, my_arg);
在上面的示例中:
首先,我们创建了一个struct tcp_pcb
的指针pcb
,表示要与自定义参数关联的TCP控制块。
然后,我们调用tcp_new()
函数来创建一个新的TCP控制块(pcb)。如果创建失败,需要根据情况处理错误。
接下来,我们初始化了一个自定义参数my_arg
。
最后,我们调用tcp_arg()
函数,传递pcb
和自定义参数my_arg
。从此之后,我们可以通过pcb->callback_arg
访问与TCP控制块关联的自定义参数。
请注意,可以在任何时候修改关联的自定义参数,只需再次调用tcp_arg()
函数即可。
tcp_output()
tcp_output()
函数的作用是将 TCP 数据包放入输出队列,并触发网络接口卡的发送操作。这个函数是 LWIP 协议栈的一部分,用于处理 TCP 连接的发送操作。
具体来说,当一个应用程序或系统调用 tcp_output()
函数时,它会将准备发送的 TCP 数据包添加到输出队列中。然后,LWIP 会负责将数据包通过适当的网络接口发送出去。
需要注意的是,tcp_output()
函数只是将数据包放入输出队列,实际的发送操作是由 LWIP 内部机制自动完成的。这意味着应用程序或系统不需要直接与网络接口卡进行交互,LWIP 会负责处理底层细节。
总之,tcp_output()
函数在 STM32 的LWIP实现中用于将 TCP 数据包放入输出队列并触发发送操作,它是 LWIP 协议栈中处理 TCP 连接发送的重要部分。
pbuf
结构体
LwIP库使用struct pbuf
结构体来表示数据包。
以下是各参数的详细说明:
-
payload
参数:
这是一个指向数据缓冲区的指针,表示数据包的有效负载(payload)。 -
len
参数:
这是一个u16_t
类型的参数,表示数据包的有效负载长度。 -
tot_len
参数:
这是一个u16_t
类型的参数,表示整个数据包的总长度。 -
next
参数:
这是一个指向下一个struct pbuf
结构体的指针,用于连接多个数据包。 -
type
参数:
这是一个枚举类型(enum pbuf_type
),表示数据包的类型,例如PBUF_POOL或PBUF_RAM。
使用场景:struct pbuf
结构体适用于在STM32上处理数据包的场景,例如网络通信中的接收和发送数据。
使用方法:
以下是使用struct pbuf
结构体的一般过程:
- 创建一个
struct pbuf
结构体对象。 - 设置有效负载数据和长度。
- 可选:连接多个数据包。
- 处理数据包。
下面是一个简单的示例,演示如何使用struct pbuf
结构体:
struct pbuf *p, *p2;
// 创建一个pbuf对象
p = pbuf_alloc(PBUF_RAW, payload_len, PBUF_POOL);
if(p == NULL) {
// 创建失败
// 处理错误...
}
// 设置有效负载数据和长度
memcpy(p->payload, payload_data, payload_len);
// 连接多个数据包(可选)
p2 = pbuf_alloc(PBUF_RAW, payload_len2, PBUF_POOL);
p->tot_len = p->len + p2->len;
pbuf_cat(p, p2);
p2 = NULL;
// 处理数据包
// ...
// 释放pbuf对象
pbuf_free(p);
p = NULL;
在上面的示例中:
首先,我们使用pbuf_alloc()
函数创建一个新的struct pbuf
结构体对象p
。我们指定了数据包的类型(PBUF_RAW),有效负载长度(payload_len)和内存分配类型(PBUF_POOL)。如果创建失败,需要根据情况处理错误。
然后,我们可以使用p->payload
指针来访问有效负载数据,并使用p->len
设置有效负载长度。注意,有效负载数据可以使用memcpy()
等函数进行复制。
接下来,我们可以通过调用pbuf_alloc()
创建另一个struct pbuf
结构体对象p2
,以支持多个数据包连接。然后,我们通过设置p->tot_len
和调用pbuf_cat()
函数将p2
连接到p
的末尾。请注意,连接后需要将p2
设置为NULL。
最后,我们可以在处理数据包的逻辑中使用pbuf
对象。
请注意,在不再需要使用pbuf
对象时,应使用pbuf_free()
函数将其释放,并将其设置为NULL。
总结一下,struct pbuf
结构体用于在STM32上表示和处理数据包。您可以通过设置有效负载数据和长度,以及连接多个数据包,来创建和操作pbuf
对象。然后,您可以在网络通信中使用pbuf
对象来接收和发送数据。
FATFS
f_mount:
在FATFS模块上注册/注销一个工作区(文件系统对象)
FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt);
参数--> fs:fs工作区(文件系统对象)指针,如果赋值为 NULL 可以取消物理设备挂载
path:注册/注销工作区的逻辑设备编号,使用设备根路径表示
opt:注册或注销选项(可选0或1),0表示不立即挂载,1表示立即挂载
f_mkfs:
格式化物理设备
FRESULT f_mkfs(const TCHAR* path, BYTE sfd, UINT au);
参数--> path:逻辑设备编号,使用设备根路径表示
sfd:0或1,0表示为硬盘设备;1表示为软盘设备
au:指定扇区大小,若为0表示通过disk_ioctl函数获取
f_mkfs()
函数是文件系统(例如FATFS)中的一个函数,用于格式化文件系统。
以下是f_mkfs()
函数的定义:
FRESULT f_mkfs (
const TCHAR* path, // 要格式化的逻辑驱动器路径
BYTE opt, // 格式化选项,如文件系统类型和扇区大小
DWORD au // 所需的簇大小,以扇区为单位
);
f_mkfs()
函数的作用是创建一个文件系统,并对指定的逻辑驱动器进行格式化。
各参数的意义如下:
path
:要格式化的逻辑驱动器的路径。它是以字符串形式表示的逻辑驱动器号,例如"0:"
表示逻辑驱动器0
,"1:"
表示逻辑驱动器1
。opt
:格式化选项,用于指定文件系统类型和扇区大小的设置。可以使用以下参数进行设置:FM_FAT
:指定为FAT12/FAT16文件系统。FM_FAT32
:指定为FAT32文件系统。FM_EXFAT
:指定为exFAT文件系统。FM_SFD
:指定为SFD (soft partition)文件系统。SSxxxx
:指定扇区大小为xxxx
字节。例如,如果扇区大小为512字节,则使用SS512
。
au
:所需的簇大小,以扇区为单位。簇是分配存储空间的最小单元。较大的簇大小可以提高存储器利用率,但会导致较小的文件浪费存储空间。
使用场景和使用方法如下:
-
使用场景:
f_mkfs()
函数在初始化文件系统时被调用,用于格式化逻辑驱动器以准备使用文件系统。 -
使用方法:在调用
f_mkfs()
之前,需要首先使用f_mount()
函数将文件系统与指定的逻辑驱动器关联起来。然后,使用f_mkfs()
函数对逻辑驱动器进行格式化。例如,可以按照以下步骤进行操作:- 调用
f_mount()
函数将文件系统与逻辑驱动器关联起来。 - 调用
f_mkfs()
函数对逻辑驱动器进行格式化。
- 调用
以下是一个示例代码片段:
FRESULT res;
FATFS fs;
char* path = "0:";
// 挂载文件系统
res = f_mount(&fs, path, 0);
if (res != FR_OK) {
// 处理挂载错误
// ...
}
// 格式化逻辑驱动器
res = f_mkfs(path, FM_FAT32, 0);
if (res != FR_OK) {
// 处理格式化错误
// ...
}
在上述示例中,首先使用f_mount()
函数将逻辑驱动器 0
与文件系统关联起来,然后调用f_mkfs()
函数将逻辑驱动器 0
格式化为FAT32文件系统。注意,具体的错误处理部分应根据实际情况进行编写。
f_open:
创建/打开一个文件对象
FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode);
参数--> fp:将创建或打开的文件对象指针
path:文件名指针,指定将创建或打开的文件名(包含文件类型后缀名)
mode:访问类型和打开方法
mode可选值:
FA_READ 指定读访问对象。可以从文件中读取数据。 与FA_WRITE结合可以进行读写访问。
FA_WRITE 指定写访问对象。可以向文件中写入数据。与FA_READ结合可以进行读写访问。
FA_OPEN_EXISTING 打开文件。如果文件不存在,则打开失败。(默认)
FA_OPEN_ALWAYS 如果文件存在,则打开;否则,创建一个新文件。
FA_CREATE_NEW 创建一个新文件。如果文件已存在,则创建失败。
FA_CREATE_ALWAYS 创建一个新文件。如果文件已存在,则它将被截断并覆盖。
f_close:
关闭一个打开的文件
FRESULT f_close (FIL *fp)
参数--> fp:将被关闭的已打开的文件对象结构的指针
f_write:
写入数据到一个已打开的文件
FRESULT f_write (FIL* fp, const void *buff, UINT btw, UINT* bw)
参数--> fp:指向将被写入的已打开的文件对象结构的指针
buff:指向存储写入数据的缓冲区的指针
btw:要写入的字节数
bw:指向返回已写入字节数的UINT变量的指针,返回为实际写入的字节数
f_read:
从一个打开的文件中读取数据
FRESULT f_read (FIL* fp, const void *buff, UINT btr, UINT* br)
参数--> fp:指向将被读取的已打开的文件对象结构的指针
buff:指向存储读取数据的缓冲区的指针
btr:要读取的字节数
br:指向返回已读取字节数的UINT变量的指针,返回为实际读取的字节数
CANopen
SDO:
sdo.c中:writeNetworkDict(); //设置sdo字典