11.物联网lwip,网卡原理
一。LWIP协议栈内存管理
1.LWIP内存管理方案
(1)堆heap
1.灰色为已使用内存
2.黑色为未使用内存
3.紫色为使用后内存
按照某种算法,把数据放在内存块中
(2)池pool
设置内存池,设置成大小相同的内存块。
2.LWIP内存管理
(1)内存池API
//内存池初始化
void memp_init(void);
//内存池分配
void *memp_malloc(memp_t type);
//内存池释放
void memp_free(memp_t type, void *mem);
(2)内存堆API
//内存堆初始化
void mem_init(void);
//内存堆分配内存
void *mem_malloc(mem_size_t size);
//内存堆释放内存
void mem_free(void *mem);
3.网络数据包的管理
(1)pbuf解释
1.pbuf就是一个描述协议栈中数据包的数据结构,LwIP 中在 pbuf.c和 pubf.h实现了协议栈数据包管理的所有函数与数据结构
2.pbuf结构体
struct pbuf{
//指向下一跳
struct pbuf *next;
//指向实际数据存放地址
void* payload;
//total全部,表示全部的长度
// p->tot_len == p->len + (p->next? p->next->tot_len: 0)获取长度
u16_t tot_len;
//本pbuf的长度
u16_t len;
//选择样式,因为存储TCP数据,UDP数据,数据链路数据存储所需大小是不一样
u8_t type;
//标识
u8_t flags;
//引用计数总是等于指针的数目
*指的是这个函数。这可以是来自应用程序的指针,
*堆栈本身,或者pbuf->链中的next指针。
u16_t ref;
}
pbuf类型--》选择不同类型,使用不同的物理结构存储,对数据处理更加高效
//pbuf.h
typedef enum {
PBUF_RAM,
PBUF_ROM,
PBUF_REF,
PBUF_POOL
} pbuf_type;
pbuf层--》选此类型是对不同报文的区分,比如PBUF_TRANSPORT传输层数据,PBUF_IP网络层数据,PBUF_LINK链路层数据,PBUF_RAW_TX物理层数据。
//pbuf.h
typedef enum {
PBUF_TRANSPORT,
PBUF_IP,
PBUF_LINK,
PBUF_RAW_TX,
PBUF_RAW
} pbuf_layer;
pbuf的申请与释放
1.申请--》使用上述的两个结构体
struct pbuf *pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type);
2.释放
u8_t pbuf_free(struct pbuf *p);
3.收缩链路的长度
void pbuf_realloc(struct pbuf *p, u16_t new_len);
4.调整有效负载指针以隐藏或显示有效负载中的标头。
u8_t pbuf_header(struct pbuf *p, s16_t header_size_increment);
5.将应用程序提供的数据复制到pbuf中。
err_t pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len);
二。netif相关结构体
1.netif flag
/**
这个netif网络接口,可以进行正常使用(lwIP可以正常使用了)
*/
#define NETIF_FLAG_UP 0x01U
/**
广播通讯的标志
*/
#define NETIF_FLAG_BROADCAST 0x02U
/**
STM32 MAC和PHY可以正常使用
*/
#define NETIF_FLAG_LINK_UP 0x04U
/**
ARP标志
*/
#define NETIF_FLAG_ETHARP 0x08U
/**
TCP/IP协议正常通信
*/
#define NETIF_FLAG_ETHERNET 0x10U
2.netif结构体
//netif.h
struct netif {
/** 链表指针 */
struct netif *next;#if LWIP_IPV4
/**
ip地址
子网掩码
网关地址
*/
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gw;
#endif /* LWIP_IPV4 */
/**
netif 数据包输入接口函数指针
*/
netif_input_fn input;
#if LWIP_IPV4
/**
netif 数据包输出接口函数指针
*/
netif_output_fn output;
#endif /* LWIP_IPV4 */
/**
链路层数据输出接口函数指针
*/
netif_linkoutput_fn linkoutput;
#if LWIP_NETIF_STATUS_CALLBACK
/**
当netif 状态发生变化时,此接口函数会调用
*/
netif_status_callback_fn status_callback;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
/**
PHY必须和交换机或者路由器或者其他具备网卡的主机相连接,我们才可能正常通信
比如 路由器突然断电,这个函数就会被调用
*/
netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_NETIF_REMOVE_CALLBACK
/**
netif 移除网络驱动接口,这个函数会被调用
*/
netif_status_callback_fn remove_callback;
#endif /* LWIP_NETIF_REMOVE_CALLBACK */
/**
主机的状态
*/
void *state;
#if LWIP_NETIF_HOSTNAME
/*
自定义的主机名称
*/
const char* hostname;
#endif /* LWIP_NETIF_HOSTNAME */
#if LWIP_CHECKSUM_CTRL_PER_NETIF
u16_t chksum_flags;
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
/**
数据链路层最大传输大小
*/
u16_t mtu;
/**
mac地址长度
*/
u8_t hwaddr_len;
/**
mac地址
*/
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
/**
当前的netif的状态,其实就是上面的netif_flag
*/
u8_t flags;
/**
网卡驱动的名称
*/
char name[2];
/**
网卡驱动的硬件编号
*/
u8_t num;
#if LWIP_IPV4 && LWIP_IGMP
/**
组播底层接口
*/
netif_igmp_mac_filter_fn igmp_mac_filter;
#endif /* LWIP_IPV4 && LWIP_IGMP */
};
2.netif API
netif_add
/**
添加网卡驱动到lwip
*/
struct netif *netif_add(struct netif *netif,const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,void *state, netif_init_fn init, netif_input_fn input);
netif_set_default
/**
把网卡恢复出厂设置,目前lwip有一套默认参数
*/
void netif_set_default(struct netif *netif);
netif_set_up&netif_set_down
/**
设置我们网卡 工作状态 是上线还是离线
*/
void netif_set_up(struct netif *netif);
void netif_set_down(struct netif *netif);
callback
// 对于用户来说,我需要自己去实现link_callback,断开连接的时候会回调这个函数
#if LWIP_NETIF_LINK_CALLBACK
void netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback);
#endif /* LWIP_NETIF_LINK_CALLBACK */
3.netif 底层接口(跟硬件打交道)
/**
初始化 网卡驱动(会调用底层驱动)
*/
err_t ethernetif_init(struct netif *netif);
/**
网卡数据输入(会调用底层接口)
*/
void ethernetif_input(void const * argument);
/**
网卡底层驱动,主要针对硬件(STM32网卡初始化会在此调用)
*/
static void low_level_init(struct netif *netif);
/**
底层网卡的数据输出,实际的数据输出,是通过pbuf进行封装管理的
*/static err_t low_level_output(struct netif *netif, struct pbuf *p);
/**
底层网卡的数据接口,当接收到网卡数据后,会通过此函数,封装为pbuf提供上层使用
*/
static struct pbuf * low_level_input(struct netif *netif);
三。LWIP网卡设计
1.tcpip_init
/**
* @工作在操作系统下的
* Initialize this module:
* - 初始化所有的子功能模块
* - 启动tcp/ip任务(tcp/ip网络协议栈的实现是一个任务里面执行的)
*
* @param 用于用户初始化的函数指针,在lwip初始化完成,tcp/ip任务开始执行就是进行调用
* @param 用户初始化相关参数传入
*/
void tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
//lwip的初始化---初始化lwip所有功能模块
lwip_init();
//用户初始化函数指针赋值,参数赋值
tcpip_init_done = initfunc;
tcpip_init_done_arg = arg;
//消息邮箱(freeRTOS是通过消息队列实现),任务与任务间消息通信,网卡收到数据,网络分层解析,我们的任务怎么知道呢,就是通过消息邮箱进行传输
if (sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
}
#if LWIP_TCPIP_CORE_LOCKING
//创建互斥锁(互斥信号量),保护共享资源的
if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
LWIP_ASSERT("failed to create lock_tcpip_core", 0);
}
#endif /* LWIP_TCPIP_CORE_LOCKING */
//这是标准的cmis接口,其实内部调用的freeRTOS的创建任务接口
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}
2.补充:lwip_init这是在裸机下的初始化
/**
* @ingroup 工作在裸机模式
* Initialize all modules.
* Use this in NO_SYS mode. Use tcpip_init() otherwise.
重点就要分析,都有哪些功能模块
*/
void lwip_init(void)
{
/* Modules initialization */
//状态初始化
stats_init();
#if !NO_SYS
//与操作系统相关的初始化
sys_init();
#endif /* !NO_SYS */
//内存堆 内存池 pbuf netif初始化
mem_init();
memp_init();
pbuf_init();
netif_init();
#if LWIP_IPV4
//ip层初始化
ip_init();
#if LWIP_ARP
//arp+以太网相关的初始化
etharp_init();
#endif /* LWIP_ARP */
#endif /* LWIP_IPV4 */
#if LWIP_RAW
//原生接口初始化
raw_init();
#endif /* LWIP_RAW */
#if LWIP_UDP
udp_init();
#endif /* LWIP_UDP */
#if LWIP_TCP
tcp_init();
#endif /* LWIP_TCP */
#if LWIP_IGMP
igmp_init();
#endif /* LWIP_IGMP */
#if LWIP_DNS
dns_init();
#endif /* LWIP_DNS */
#if PPP_SUPPORT
ppp_init();
#endif
#if LWIP_TIMERS
//lwip内部有很多超时机制,就是通过下面这个timeouts实现的(一个软件定时器)
sys_timeouts_init();
#endif /* LWIP_TIMERS */
}
3.HAL库实现lwip的初始化
/**
* HAL库实现的lwip初始化函数
*/
void MX_LWIP_Init(void)
{
/* IP 地址初始化 */
IP_ADDRESS[0] = 192;
IP_ADDRESS[1] = 168;
IP_ADDRESS[2] = 1;
IP_ADDRESS[3] = 10;
NETMASK_ADDRESS[0] = 255;
NETMASK_ADDRESS[1] = 255;
NETMASK_ADDRESS[2] = 255;
NETMASK_ADDRESS[3] = 0;
GATEWAY_ADDRESS[0] = 192;
GATEWAY_ADDRESS[1] = 168;
GATEWAY_ADDRESS[2] = 1;
GATEWAY_ADDRESS[3] = 1;
/* 初始化lwip协议栈 */
tcpip_init( NULL, NULL );/*
数组格式的IP地址转换为lwip格式的地址
*/
IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);/*
装载网卡驱动,并初始化网卡
*/
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);/*
gnetif注册为默认网卡驱动
*/
netif_set_default(&gnetif);
// 判断phy和mac层是否正常工作
if (netif_is_link_up(&gnetif))
{
/*
netif 网卡驱动可以正常使用,上线
*/
netif_set_up(&gnetif);
}
else
{
/*
netif 网卡驱动下线
*/
netif_set_down(&gnetif);
}/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
4.以太网的初始化 ethernetif_init
/**
以太网初始化 这是一个分层接口,最终会调用底层接口
*/
err_t ethernetif_init(struct netif *netif)
{#if LWIP_IPV4
#if LWIP_ARP || LWIP_ETHERNET
//arp相关的函数接口赋值
#if LWIP_ARP
netif->output = etharp_output;
#else
netif->output = low_level_output_arp_off;
#endif /* LWIP_ARP */
#endif /* LWIP_ARP || LWIP_ETHERNET */
#endif /* LWIP_IPV4 */
//链路层数据输出函数接口赋值
netif->linkoutput = low_level_output;
/*
底层接口初始化
*/
low_level_init(netif);
return ERR_OK;
}
low_level_init
/**
硬件初始化,其实就STM32 ETH外设初始化
*/
static void low_level_init(struct netif *netif)
{
uint32_t regvalue = 0;
HAL_StatusTypeDef hal_eth_init_status;
/* Init ETH */uint8_t MACAddr[6] ;
heth.Instance = ETH;
heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
heth.Init.PhyAddress = DP83848_PHY_ADDRESS;
MACAddr[0] = 0x00;
MACAddr[1] = 0x80;
MACAddr[2] = 0xE1;
MACAddr[3] = 0x00;
MACAddr[4] = 0x00;
MACAddr[5] = 0x00;
heth.Init.MACAddr = &MACAddr[0];
heth.Init.RxMode = ETH_RXINTERRUPT_MODE;
heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;/* USER CODE BEGIN MACADDRESS */
/* USER CODE END MACADDRESS */
hal_eth_init_status = HAL_ETH_Init(&heth);if (hal_eth_init_status == HAL_OK)
{
/*
重点在这,当初始化成功后,会置位flag,同时在tcp/ip
初始化完毕后,会进行判断,此标志位决定网卡驱动是否
可以正常使用
*/
netif->flags |= NETIF_FLAG_LINK_UP;
}
/* Initialize Tx Descriptors list: Chain Mode */
HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
/* Initialize Rx Descriptors list: Chain Mode */
HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
#if LWIP_ARP || LWIP_ETHERNET/*
MAC地址初始化
*/
netif->hwaddr_len = ETH_HWADDR_LEN;
netif->hwaddr[0] = heth.Init.MACAddr[0];
netif->hwaddr[1] = heth.Init.MACAddr[1];
netif->hwaddr[2] = heth.Init.MACAddr[2];
netif->hwaddr[3] = heth.Init.MACAddr[3];
netif->hwaddr[4] = heth.Init.MACAddr[4];
netif->hwaddr[5] = heth.Init.MACAddr[5];
/* maximum transfer unit */
netif->mtu = 1500;
/* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */
/*
二值信号量,用于信息同步
当网卡接口到数据后,会释放二值信号量
让其他任务进行解析
*/
osSemaphoreDef(SEM);
s_xSemaphore = osSemaphoreCreate(osSemaphore(SEM), 1);/*
创建网卡数据接收解析任务-ethernetif_input
*/
osThreadDef(EthIf, ethernetif_input, osPriorityRealtime, 0, INTERFACE_THREAD_STACK_SIZE);
osThreadCreate (osThread(EthIf), netif);
/*
使能 网卡 发送和接口
*/
HAL_ETH_Start(&heth);
/****
上面的都是针对STM32 ETH外设进行初始化
但是实际网络交互是用过PHY
下面就是初始化PHY
****/
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, ®value);
regvalue |= (PHY_MICR_INT_EN | PHY_MICR_INT_OE);/* Enable Interrupts */
HAL_ETH_WritePHYRegister(&heth, PHY_MICR, regvalue );
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MISR, ®value);
regvalue |= PHY_MISR_LINK_INT_EN;
/* Enable Interrupt on change of link status */
HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue);/* USER CODE BEGIN PHY_POST_CONFIG */
/* USER CODE END PHY_POST_CONFIG */#endif /* LWIP_ARP || LWIP_ETHERNET */
/* USER CODE BEGIN LOW_LEVEL_INIT */
/* USER CODE END LOW_LEVEL_INIT */
}
底层数据收发
HAL_ETH_RxCpltCallback
/**
* @brief Ethernet Rx Transfer completed callback
* @param heth: ETH handle
* @retval None
*/
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
osSemaphoreRelease(s_xSemaphore);
}
ethernetif_input
/**
*/
void ethernetif_input(void const * argument)
{
struct pbuf *p;
struct netif *netif = (struct netif *) argument;
for( ;; )
{
if (osSemaphoreWait(s_xSemaphore, TIME_WAITING_FOR_INPUT) == osOK)
{
do
{
p = low_level_input( netif );
if (p != NULL)
{
if (netif->input( p, netif) != ERR_OK )
{
pbuf_free(p);
}
}
} while(p!=NULL);
}
}
}
low_level_input
/**
*/
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
struct pbuf *q = NULL;
uint16_t len = 0;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i=0;
/*
通过HAL库,获取网卡帧数据
*/
if (HAL_ETH_GetReceivedFrame_IT(&heth) != HAL_OK)
return NULL;
/*
获取网卡数据超度,及内存地址
*/
len = heth.RxFrameInfos.length;
buffer = (uint8_t *)heth.RxFrameInfos.buffer;
//网卡中数据有效
if (len > 0)
{
/*
网卡数据不能大于1500
属于原始层接口
*/
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
}
//如果pbuf创建成功,则从ETH中拷贝数据到pbuf里,最终把pbuf返回给上层应用
if (p != NULL)
{
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
bufferoffset = 0;
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/
while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
/* Copy data to pbuf */
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
/* Point to next descriptor */
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy remaining data in pbuf */
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}
}
/* Release descriptors to DMA */
/* Point to first descriptor */
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i=0; i< heth.RxFrameInfos.SegCount; i++)
{
dmarxdesc->Status |= ETH_DMARXDESC_OWN;
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
/* Clear Segment_Count */
heth.RxFrameInfos.SegCount =0;
/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
{
/* Clear RBUS ETHERNET DMA flag */
heth.Instance->DMASR = ETH_DMASR_RBUS;
/* Resume DMA reception */
heth.Instance->DMARPDR = 0;
}
return p;
}
low_level_output
/**
*/
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = heth.TxDesc;
bufferoffset = 0;
/* copy frame from pbufs to driver buffers */
for(q = p; q != NULL; q = q->next)
{
/* Is this buffer available? If not, goto error */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
/* Get bytes in current lwIP buffer */
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of data to copy is bigger than Tx buffer size*/
while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
/* Copy data to Tx buffer*/
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
/* Point to next descriptor */
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
/* Check if the buffer is available */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy the remaining bytes */
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
/*
把pbuf里面的数据,发送到ETH外设里面
*/
HAL_ETH_TransmitFrame(&heth, framelength);
errval = ERR_OK;
error:
/* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
/* Clear TUS ETHERNET DMA flag */
heth.Instance->DMASR = ETH_DMASR_TUS;/* Resume DMA transmission*/
heth.Instance->DMATPDR = 0;
}
return errval;
}
解释:lwip的移植与裁剪
1.移植文件的存放地
(1)打开工程文件,进入根目录下
(2)middlewares文件夹下就是移植所需要的文件,有下图可知有Freertos与lwip
(3)这里主要看LWIP的移植,src为经常使用的.c与.h文件,system即为移植文件存方地。
2.移植步骤
(1)网卡驱动 ETH以太网接口
<1>lwip
<2>ethernetif
(2)操作系统 Freertos配置
<1>sys.arch.h
<2>sys.arch.c
(3)配置选项
<1>lwipopt 常用的宏定义放在这里
<2>opt 规定的宏定义存放
相关文章:

11.物联网lwip,网卡原理
一。LWIP协议栈内存管理 1.LWIP内存管理方案 (1)堆heap 1.灰色为已使用内存 2.黑色为未使用内存 3.紫色为使用后内存 按照某种算法,把数据放在内存块中 (2)池pool 设置内存池,设置成大小相同的内存块。 2…...

视频监控/视频汇聚/视频云存储EasyCVR平台接入华为ivs3800平台提示400报错,该如何解决?
开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,视频云存储/安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多路视频…...

WordPress主题Zing V2.2.1/模块化WordPress响应式通用企业商城主题
WordPress主题Zing V2.2.1,模块化WordPress响应式通用企业商城主题。 功能介绍 百度熊掌号文章实时推送、原创保护 多设备支持自适应布局,支持电脑、Pad、手机以及各种浏览器 SEO优化首页、文章、页面、分类均支持自定义标题、关键字和描述 速度优化…...

【无需公网IP】在树莓派上搭建Web站点
目录 1.概述 2.使用 Raspberry Pi Imager 安装 Raspberry Pi OS 3.设置 Apache Web 服务器 3.1测试 web 站点 3.2安装静态样例站点 3.3将web站点发布到公网 3.4安装 Cpolar 3.5cpolar进行token认证 3.6生成cpolar随机域名网址 3.7生成cpolar二级子域名 3.8将参数保存…...

出差在外,远程访问企业局域网象过河ERP系统「内网穿透」
文章目录 概述1.查看象过河服务端端口2.内网穿透3. 异地公网连接4. 固定公网地址4.1 保留一个固定TCP地址4.2 配置固定TCP地址 5. 使用固定地址连接 概述 ERP系统对于企业来说重要性不言而喻,不管是财务、生产、销售还是采购,都需要用到ERP系统来协助。…...

Vue2-replace属性、编程式路由导航、缓存路由组件、两个新的生命周期钩子、路由守卫、路由器工作模式
🥔:如果事与愿违,那一定是上天另有安排 更多Vue知识请点击——Vue.js VUE2-Day13 router-link的replace属性编程式路由导航1、什么是编程式路由导航2、如何编码3、使用案例示例说明 缓存路由组件两个新的生命周期钩子路由守卫1、路由元信息2、…...

C语言:指针的运算
一、指针 或 - 整数 指针 或 - 整数表示指针跳过几个字节(具体跳过几个字节由指针类型决定) 本文不做具体讲解,详解跳转链接: 《C语言:指针类型的意义》 二、指针 - 指针 前提条件:指针类型相同并且指向同…...

设计模式的使用——模板方法模式+动态代理模式
一、需求介绍 现有自己写的的一套审批流程逻辑,由于代码重构,需要把以前的很多业务加上审批的功能,再执行完审批与原有业务之后,生成一个任务,然后再统一处理一个任务(本来是通过数据库作业去处理的&#x…...

C++学习记录——삼십 智能指针
文章目录 1、为什么需要智能指针?2、内存泄漏3、智能指针的使用及原理1、RAII思想2、拷贝问题1、unique_ptr2、shared_ptr1、多线程2、循环引用3、定制删除器 1、为什么需要智能指针? 看一个场景 int div() {int a, b;cin >> a >> b;if (b…...

插件式架构 与 ReSharper、Visual Studio的故事
文章首发地址 ReSharper和Visual Studio的故事 ReSharper是一款由JetBrains公司开发的Visual Studio插件,它主要用于提高Visual Studio的开发效率和改善代码质量。ReSharper在早期的版本中被称为"Omea Code",它最初是JetBrains一个研究项目的…...

Python UDP编程
前面我们讲了 TCP 编程,我们知道 TCP 可以建立可靠连接,并且通信双方都可以以流的形式发送数据。本文我们再来介绍另一个常用的协议--UDP。相对TCP,UDP则是面向无连接的协议。 UDP 协议 我们来看 UDP 的定义: UDP 协议ÿ…...

结构体(个人学习笔记黑马学习)
1、结构体的定义和使用 #include <iostream> using namespace std; #include <string>struct Student {string name;int age;int score; }s3;int main() {//1、struct Student s1;s1.name "张三";s1.age 18;s1.score 100;cout << "姓名&a…...

小白带你学习linux的PXE装机
目录 目录 一、PXE是什么? 二、PXE的组件: 1、vsftpd/httpd/nfs 2、tftp 3、dhcp 三、配置dhcp 1、关闭防火墙与selinux和配置本地yum源 2、安装dhcp服务 3、配置dhcp配置文件 四、配置vsftpd 五、配置tftp 1、安装tftp-server 2、启动tft…...

华为鲲鹏服务器
1.简介 鲲鹏通用计算平台提供基于鲲鹏处理器的TaiShan服务器、鲲鹏主板及开发套件。硬件厂商可以基于鲲鹏主板发展自有品牌的产品和解决方案;软件厂商基于openEuler开源OS以及配套的数据库、中间件等平台软件发展应用软件和服务;鲲鹏开发套件可帮助开发…...

Python金币小游戏
游戏规则:移动挡板接住金币 游戏截图: 详细代码如下: import pygame.freetype import sys import randompygame.init() screen pygame.display.set_mode((600, 400)) pygame.display.set_caption(game) p 0 i1 0 s 0 t 0 f1 pygame.f…...

Modbus转Profinet网关在大型自动化仓储项目应用案例
在自动化仓储项目中,Modbus是一种常见的通信协议,用于连接各种设备,例如传感器、PLC和人机界面。然而,Modbus协议只支持串行通信,并且数据传输速度较慢。为了提高通信效率和整体系统性能,许多大型仓储项目选…...

Java 并发 ThreadLocal 详解
文章首发于个人博客,欢迎访问关注:https://www.lin2j.tech 简介 ThreadLocal 即线程本地变量的意思,常被用来处理线程安全问题。ThreadLocal 的作用是为多线程中的每一个线程都创建一个线程自身才能用的实例对象,通过线程隔离的…...

JWT 技术的使用
应用场景:访问某些页面,需要用户进行登录,那我们如何知道用户有没有登录呢,这时我们就可以使用jwt技术。用户输入的账号和密码正确的情况下,后端根据用户的唯一id生成一个独一无二的token,并返回给前端&…...

机器学习深度学习——NLP实战(自然语言推断——微调BERT实现)
👨🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——针对序列级和词元级应用微调BERT 📚订阅专栏:机器学习&&深度学习 希望文…...

如何在windows下使用masm和link对汇编文件进行编译
前言 32位系统带有debug程序,可以进行汇编语言和exe的调试。但真正的汇编编程是“编辑汇编程序文件(.asm)->编译生成obj文件->链接生成exe文件”。下面,我就来说一下如何在windows下使用masm调试,使用link链接。 1、下载相应软件 下载…...

Golang字符串基本处理方法
Golang的字符串处理 字符串拼接 两种方法:strings.Join方法和’方法 package mainimport ("fmt""strings" )func main() {num : 20strs : make([]string, 0)for i : 0; i < num; i {strs append(strs, "fht")}//string.join拼…...

算法训练营第三十九天(8.30)| 动态规划Part09:购买股票
Leecode 123.买卖股票的最佳时机 III 123.买卖股票的最佳时机III 123.买卖股票的最佳时机III 题目地址:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 题目类型:股票问题 class Solution { public:int maxProfit(vector<…...

renren-fast-vue环境升级后,运行正常打包后,访问页面空白
网上各种环境,路径都找了一遍,也没成功。后来发现升级后打包的dist文件结构发生了变化, 1.最开始正常版本是这样 2.升级后是这样,少了日期文件夹 3.问题:打包后的index.html中引入的是config文件夹,而打…...

Uniapp笔记(三)uniapp语法2
一、本节项目预备知识 1、组件生命周期 1.1、什么是生命周期 生命周期(Life Cycle)是指一个对象从创建-->运行-->销毁的整个阶段,强调的是一个时间段 我们可以把每个uniapp应用运行的过程,也概括为生命周期 小程序的启动,表示生命周…...

windows【ftp-FTP】添加配置流程【iis服务】
第一步:自己安装iis服务和ftp服务【自己百度搜索】 第二步:添加ftp站点【配置主动端口默认为21】 第三方配置:ftp被动端口【这里设置为3000-4000】请在防火墙开放此端口【如果是阿里云请在阿里云的后天也开通此端口】【护卫神一般使用55000…...

mysql视图的创建和选项配置详解
在 MySQL 中,可以使用 CREATE VIEW 语句来创建视图。基本的语法如下: CREATE[OR REPLACE][ALGORITHM {UNDEFINED | MERGE | TEMPTABLE}][DEFINER {user | CURRENT_USER}][SQL SECURITY { DEFINER | INVOKER }]VIEW view_name [(column_list)]AS selec…...

Python正则表达式中re.sub自定义替换方法正确使用方法
大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说,直接开搞,如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 在使用正则替换时,有时候需要将匹配的结果做对应处理,便可以使用自定义替换方法。 re.sub的用法为&…...

hyperf 十五 验证器
官方文档:Hyperf 验证器报错需要配合多语言使用,创建配置自动生成对应的语言文件。 一 安装 composer require hyperf/validation:v2.2.33 composer require hyperf/translation:v2.2.33php bin/hyperf.php vendor:publish hyperf/translation php bi…...

ssh访问远程宿主机的VMWare中NAT模式下的虚拟机
1.虚拟机端配置 1.1设置虚拟机的网络为NAT模式 1.2设置虚拟网络端口映射(NAT) 点击主菜单的编辑-虚拟网络编辑器: 启动如下对话框,选中NAT模式的菜单项,并点击NAT设置: 点击添加,为我们的虚拟机添加一个端口映射。…...

【一等奖方案】大规模金融图数据中异常风险行为模式挖掘赛题「NUFE」解题思路
第十届CCF大数据与计算智能大赛(2022 CCF BDCI)已圆满结束,大赛官方竞赛平台DataFountain(简称DF平台)正在陆续释出各赛题获奖队伍的方案思路,欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…...