打印
[应用相关]

实战STM32F4以太网DP83848配合LWIP

[复制链接]
4691|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
晓伍|  楼主 | 2021-8-1 14:11 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
目的:实现STM32F407+FreeRTOS+Ethernet(DP83848)+Lwip实现socket通信,在实现之前我们先来了解下几点储备知识



一. 以太网行业标准MII/RMII
1 以太网接口MII,RMII
MII即“媒体独立接口”,也叫“独立于介质的接口”。它是IEEE-802.3定义的以太网行业标准。它包括一个数据接口,以及一个MAC和PHY之间的管理接口。

RMII全称为“简化的媒体独立接口”,是IEEE-802.3u标准中除MII接口之外的另一种实现。

1.1. 独立于介质的接口(MII)
独立于介质的接口(MII)用于MAC与外接的PHY互联,支持10Mbit/s和100Mbit/s数据传输模式。MII的信号线如下图所示:




  • MII_TX_EN:传输使能信号,此信号必需与数据前导符的起始位同步出现,并在传输完毕前一直保持。
  • MII_TX_CLK:发送数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
  • MII_TXD[3:0]:发送数据线,每次传输4位数据,数据在MII_TX_EN信号有效时有效。MII_TXD[0]是数据的最低位,MII_TXD[3]是最高位。当MII_TX_EN信号无效时,PHY忽略传输的数据。
  • MII_RX_ER:接收出错信号,保持一个或多个时钟周期(MII_RX_CLK)的有效状态,表明MAC在接收过程中检测到错误。具体错误原因需配合MII_RX_DV的状态及MII_RXD[3:0]的数据值。
  • MII_RX_DV:接收数据使能信号,由PHY控制,当PHY准备好数据供MAC接收时,使能该信号。此信号必需和帧数据的首位同步出现,并保持有效直到数据传输完成。在传送最后4位数据后的第一个时钟之前,此信号必需变为无效状态。为了正确的接收一个帧,有效电平不能滞后于数据线上的SFD位出现。
  • MII_RX_CLK:接收数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
  • MII_RXD[3:0]:接收数据线,每次接收4位数据,数据在MII_RX_DV信号有效时有效。MII_RXD[0]是数据的最低位,MII_RXD[3]是最高位。当MII_RX_EN无效,而MII_RX_ER有效时,MII_RXD[3:0]数据值代表特定的信息(请参考表194)。
  • MII_CRS:载波侦听信号,仅工作在半双工模式下,由PHY控制,当发送或接收的介质非空闲时,使能此信号。 PHY必需保证MII_CRS信号在发生冲突的整个时间段内都保持有效,不需要此信号与发送/接收的时钟同步。
  • MII_COL:冲突检测信号,仅工作在半双工模式下,由PHY控制,当检测到介质发生冲突时,使能此信号,并且在整个冲突的持续时间内,保持此信号有效。此信号不需要和发送/接收的时钟同步。



1.2 精简的独立于介质的接口(RMII)
精简的独立于介质接口(RMII)规范减少了以太网通信所需要的引脚数。根据IEEE802.3标准,MII接口需要16个数据和控制信号引脚,而RMII标准则将引脚数减少到了7个。RMII具有以下特性:



时钟信号需要提高到50MHz.MAC和外部的以太网PHY需要使用同样的时钟源 ,使用2位宽度的数据收发   

RMII的信号线如下图所示:



1.3 时钟源
1)MII时钟源

为了产生TX_CLK和RX_CLK时钟信号,外接的PHY模块必需有来自外部的25MHz时钟驱动。该时钟不需要与MAC时钟相同。可以使用外部的25MHz晶体。当时钟来源MCO引脚时需配置合适的PLL,保证MCO引脚输出的时钟为25MHZ。

2)RMII时钟源

通过将相同的时钟源接到MAC和以太网PHY的REF_CLK引脚保证两者时钟源的同步。可以通过外部的50MHZ信号或者GD32F107xx微控制器的MCO引脚提供这一时钟。当时钟来源MCO引脚时需配置合适的PLL,保证MCO引脚输出的时钟为50MHZ。   

3)总结

采用MII接口,PYH的时钟频率要求25M,不需要与MAC层时钟一致。

采用RMII接口,PYH的时钟频率要求50M,需与MAC层时钟一致,通常从MAC层获取该时钟源。


使用特权

评论回复
沙发
晓伍|  楼主 | 2021-8-1 14:12 | 只看该作者
二. 以太网芯片(DP83848介绍)


1. DP83848芯片概述
DP83848是TI出的一款以太网物理层IC,有以下feature



一般工规芯片选择DP83848I就行了



2. DP83848引脚介绍
2.1 Serial Management Interface


2.2 MAC Data Interface







2.3 Clock Interface


2.4 LED Interface


2.5 Reset and Power Down



使用特权

评论回复
板凳
晓伍|  楼主 | 2021-8-1 14:15 | 只看该作者
3. DP83848中断寄存器

这个在后面移植的low_level_init会用到



使用特权

评论回复
地板
晓伍|  楼主 | 2021-8-1 14:16 | 只看该作者
三. CubeMx配置以太网芯片DP838481. DP83848 GPIO配置

HR91105A是这个

CubeMx GPIO配置如下(通过原理图可以看到是RMII接口):


使用特权

评论回复
5
晓伍|  楼主 | 2021-8-1 14:17 | 只看该作者
2. Cubemx Advanced Paramter配置
这里会牵扯到三个配置:

2.1 External PHY Configuration


此部分一共牵扯到以下部分:

PHY: 外部PHY芯片,我们开发板是选择的这个DP83848,所以默认可以直接勾选上

PHY Address Value: 外部PHY芯片的地址,可以看到DP83848芯片是根据map来的,默认PHYAD0是默认上拉的,PHYAD[4:1]是默认下来的,所以默认地址是0x01



其他默认配置就行了!

2.2 Common:External PHY Configuration


这个我们选择默认就好

2.3 Extended:External PHY Configuration


这个我们选择默认就好


使用特权

评论回复
6
晓伍|  楼主 | 2021-8-1 14:18 | 只看该作者
四. lwip介绍
LwIP 全名:Light weight IP,意思是轻量化的 TCP/IP 协议,是瑞典计算机科学院 (SICS) 的 Adam Dunkels 开发的一个小型开源的 TCP/IP 协议栈。LwIP 的设计初衷是:用少量的资源消耗实现一个较为完整的TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性,实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP 既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。

LwIP 具有主要特性:

  • 支持 ARP 协议(以太网地址解析协议)。
  • 支持 ICMP 协议(控制报文协议),用于网络的调试与维护。
  • 支持 IGMP 协议(互联网组管理协议),可以实现多播数据的接收。
  • 支持 UDP 协议 (用户数据报协议)。
  • 支持 TCP 协议 (传输控制协议),包括阻塞控制、RTT 估算、快速恢复和快速转发。
  • 支持 PPP 协议(点对点通信协议),支持 PPPoE。
  • 支持 DNS(域名解析)。
  • 支持 DHCP 协议,动态分配 IP 地址。
  • 支持 IP 协议,包括 IPv4、IPv6 协议,支持 IP 分片与重装功能,多网络接口下的数据包转发。
  • 支持 SNMP 协议(简单网络管理协议)。
  • 支持 AUTOIP,自动 IP 地址配置。
  • 提供专门的内部回调接口 (Raw API),用于提高应用程序性能。
  • 提供可选择的 Socket API、NETCONN API (在多线程情况下使用) 。

LwIP 在嵌入式中使用有以下优点:

  • 资源开销低,即轻量化。LwIP 内核有自己的内存管理策略和数据包管理策略,使得内核处理数据包的效率很高。另外,LwIP 高度可剪裁,一切不需要的功能都可以通过宏编译选项去掉。LwIP 的流畅运行需要40KB 的代码 ROM 和几十 KB 的 RAM,这让它非常适合用在内存资源受限的嵌入式设备中。
  • 支持的协议较为完整。几乎支持 TCP/IP 中所有常见的协议,这在嵌入式设备中早已够用。
  • 实现了一些常见的应用程序:DHCP 客户端、DNS 客户端、HTTP 服务器、MQTT 客户端、TFTP 服务器、SNTP 客户端等等。
  • 同时提供了三种编程接口:RAW API、NETCONN API (注:NETCONN API 即为 SequentialAPI,为了统一,下文均采用 NETCONN API)和 Socket API。这三种 API 的执行效率、易用性、可移植性以及时空间的开销各不相同,用户可以根据实际需要,平衡利弊,选择合适的 API 进行网络应用程序的开发。
  • 高度可移植。其源代码全部用 C 实现,用户可以很方便地实现跨处理器、跨编译器的移植。另外,它对内核中会使用到操作系统功能的地方进行了抽象,使用了一套自定义的 API,用户可以通过自己实现这些 API,从而实现跨操作系统的移植工作。
  • 开源、免费,用户可以不用承担任何商业风险地使用它。
  • 相比于嵌入式领域其它的 TCP/IP 协议栈,比如 uC-TCP/IP、FreeRTOS-TCP 等,LwIP 的发展历史要更悠久一些,得到了更多的验证和测试。LwIP 被广泛用在嵌入式网络设备中,国内一些物联网公司推出的物联网操作系统,其 TCP/IP 核心就是 LwIP;物联网知名的 WiFi模块 ESP8266,其 TCP/IP 固件,使用的就是 LwIP。



LwIP 尽管有如此多的优点,但它毕竟是为嵌入式而生,所以并没有很完整地实现 TCP/IP 协议栈。相比于 Linux 和 Windows 系统自带的 TCP/IP 协议栈,LwIP 的功能不算完整和强大。但对于大多数物联网领域的网络应用程序,LwIP 已经足够了。


使用特权

评论回复
7
晓伍|  楼主 | 2021-8-1 14:20 | 只看该作者
1.文件列表
可以通过这个链接去下载lwip的代码:http://download.savannah.nongnu.org/releases/lwip/

打开后就是这个模样:



文件列表大体分为几类:

1)drivers ,主要是提供一款32位的MCU(MCF5223X)部分驱动以及一款Eth的LAN芯片(CS8900A)

2)older_versions,主要是提供一些旧(1.4.0以前的版本)的lwip版本以及Contrib source code

3)contrib-xxxx,主要提供一些辅助代码(示例/移植等)

4)lwip-xxxxx,lwip对应版本的代码

9605961063d2a6ffa7.png (42.81 KB )

9605961063d2a6ffa7.png

使用特权

评论回复
8
晓伍|  楼主 | 2021-8-1 14:23 | 只看该作者
五. STM32F407在没有操作系统下移植lwip
1.Cubemx配置过程
1.1 RCC配置(选择外部晶振)


1.2 SYS配置(时基选择Systick)


1.3 Eth配置
我们以太网配置,我们已经在前面(CubeMx配置以太网芯片DP83848)做了说明,所以不再重复!

1.4 UART1配置(主要用来debug)


在这个基础上需要自己添加fputc函数(勾选micro lib)

int fputc(int ch, FILE *fp)
{
    if (fp == stdout)
    {
        /*If you want to output a line feed, you can automatically add a carriage
        return to prevent the horizontal position of some terminal terminals
        from changing after line feed.*/
        if (ch == '\n')
        {
            while ((USART1->SR & USART_SR_TXE) == 0);
            USART1->DR = '\r';
        }
        /* Output characters to USART1 */
        while ((USART1->SR & USART_SR_TXE) == 0);
        USART1->DR = ch;
    }
    return ch;
}
1.5 lwip配置
此部分我们暂时除了DHCP部分,我们其他都用默认的!

这里有一个注意的点:IP地址的第三个域要根据路由器来决定,比如我的路由器IP地址是192.168.1.1,那么我可以随意配置成192.168.1.x(其中x在这个局域网中不能有重复地址)



1.6 stm32f407 clock


有几个注意的点:

①需要根据外部晶振来填写HSE的value,我的开发板外部晶振是25M
②选择HSE
③选择PLLCLK倍频
④选择Max 168M,让cubemx自己配置就行了
1.6 在main函数的while loop中加上lwip的处理MX_LWIP_Process
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */
  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_LWIP_Init();
  /* USER CODE BEGIN 2 */
        printf("STM32F407 ETH LWIP RUNING...\n");
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        MX_LWIP_Process();  /* 嗨,我在这里哦,添加在这里,别找不到我! */
  }
  /* USER CODE END 3 */
}
丰收的时候到了哇!!!我们来ping下试试看



使用特权

评论回复
9
晓伍|  楼主 | 2021-8-1 14:24 | 只看该作者
2.自己移植lwip过程

2.1 配置lwip的宏文件lwipopts.h(可以直接把这个copy到你的noos的lwip中)

#ifndef __LWIPOPTS__H__
#define __LWIPOPTS__H__
#include "main.h"
/*-----------------------------------------------------------------------------*/
/* Current version of LwIP supported by CubeMx: 2.1.2 -*/
/*-----------------------------------------------------------------------------*/
/* Within 'USER CODE' section, code will be kept by default at each generation */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#ifdef __cplusplus
extern "C" {
#endif
/* STM32CubeMX Specific Parameters (not defined in opt.h) ---------------------*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- WITH_RTOS disabled (Since FREERTOS is not set) -----*/
#define WITH_RTOS 0
/*----- CHECKSUM_BY_HARDWARE enabled -----*/
#define CHECKSUM_BY_HARDWARE 1
/*-----------------------------------------------------------------------------*/
/* LwIP Stack Parameters (modified compared to initialization value in opt.h) -*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- Value in opt.h for NO_SYS: 0 -----*/
#define NO_SYS 1 /* NO_SYS 表示无操作系统模拟层,这个宏非常重要,因为无操作系统与有操作系统的移植和编写是完全不一样的,我们现在是无操作系统移植,所以将这个宏定义为 1。*/
/*----- Value in opt.h for SYS_LIGHTWEIGHT_PROT: 1 -----*/
#define SYS_LIGHTWEIGHT_PROT 0
/*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
#define MEM_ALIGNMENT 4 /* 内存对齐,按照 4 字节对齐 */
/*----- Value in opt.h for LWIP_ETHERNET: LWIP_ARP || PPPOE_SUPPORT -*/
#define LWIP_ETHERNET 1
/*----- Value in opt.h for LWIP_DNS_SECURE: (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT) -*/
#define LWIP_DNS_SECURE 7
/*----- Value in opt.h for TCP_SND_QUEUELEN: (4*TCP_SND_BUF + (TCP_MSS - 1))/TCP_MSS -----*/
#define TCP_SND_QUEUELEN 9
/*----- Value in opt.h for TCP_SNDLOWAT: LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -*/
#define TCP_SNDLOWAT 1071
/*----- Value in opt.h for TCP_SNDQUEUELOWAT: LWIP_MAX(TCP_SND_QUEUELEN)/2, 5) -*/
#define TCP_SNDQUEUELOWAT 5
/*----- Value in opt.h for TCP_WND_UPDATE_THRESHOLD: LWIP_MIN(TCP_WND/4, TCP_MSS*4) -----*/
#define TCP_WND_UPDATE_THRESHOLD 536
/*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
#define LWIP_NETIF_LINK_CALLBACK 1
/*----- Value in opt.h for LWIP_NETCONN: 1 -----*/
#define LWIP_NETCONN 0
/*----- Value in opt.h for LWIP_SOCKET: 1 -----*/
#define LWIP_SOCKET 0
/*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
#define RECV_BUFSIZE_DEFAULT 2000000000
/*----- Value in opt.h for LWIP_STATS: 1 -----*/
#define LWIP_STATS 0
/*----- Value in opt.h for CHECKSUM_GEN_IP: 1 -----*/
#define CHECKSUM_GEN_IP 0
/*----- Value in opt.h for CHECKSUM_GEN_UDP: 1 -----*/
#define CHECKSUM_GEN_UDP 0
/*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/
#define CHECKSUM_GEN_TCP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP: 1 -----*/
#define CHECKSUM_GEN_ICMP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/
#define CHECKSUM_GEN_ICMP6 0
/*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/
#define CHECKSUM_CHECK_IP 0
/*----- Value in opt.h for CHECKSUM_CHECK_UDP: 1 -----*/
#define CHECKSUM_CHECK_UDP 0
/*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/
#define CHECKSUM_CHECK_TCP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP: 1 -----*/
#define CHECKSUM_CHECK_ICMP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/
#define CHECKSUM_CHECK_ICMP6 0
/*-----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
#ifdef __cplusplus
}
#endif
#endif /*__LWIPOPTS__H__ */


使用特权

评论回复
10
晓伍|  楼主 | 2021-8-1 14:25 | 只看该作者
2.2 配置字节对齐等宏cc.h

#ifndef __CC_H__
#define __CC_H__
#include "cpu.h"
#include <stdlib.h>
#include <stdio.h>
typedef int sys_prot_t;
#define LWIP_PROVIDE_ERRNO
#if defined (__GNUC__) & !defined (__CC_ARM)
#define LWIP_TIMEVAL_PRIVATE 0
#include <sys/time.h>
#endif
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT  
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
                                     x, __LINE__, __FILE__); } while(0)
/* Define random number generator function */
#define LWIP_RAND() ((u32_t)rand())
#endif /* __CC_H__ */


使用特权

评论回复
11
晓伍|  楼主 | 2021-8-1 14:26 | 只看该作者
2.3 把eth对接到lwip init/output/input函数中
主要牵扯到的函数(这三个函数都在ethernetif.c中)是:

1)low_level_init

2)low_level_output

3)low_level_input

low_level_init 就是底层初始化,代码如下:

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; /* 设置自动识别速度(10M/100M) */
  heth.Init.Speed = ETH_SPEED_100M;
  heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;  /* 设置全双工 */
  heth.Init.PhyAddress = DP83848_PHY_ADDRESS;  /* 设置物理地址 */
  MACAddr[0] = 0x00;
  MACAddr[1] = 0x80;
  MACAddr[2] = 0xE1;
  MACAddr[3] = 0x00;
  MACAddr[4] = 0x00;
  MACAddr[5] = 0x00;  /* 设置MAC地址 */
  heth.Init.MACAddr = &MACAddr[0];
  heth.Init.RxMode = ETH_RXPOLLING_MODE; /* 设置模式为轮询 */
  heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; /* 设置以太网硬件校验 */
  heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;  /* 接口为RMII */
  /* USER CODE BEGIN MACADDRESS */
  /* USER CODE END MACADDRESS */
  hal_eth_init_status = HAL_ETH_Init(&heth);  
  if (hal_eth_init_status == HAL_OK)
  {
    /* Set netif link flag */
    netif->flags |= NETIF_FLAG_LINK_UP;
  }
  /* Initialize Tx Descriptors list: Chain Mode */
  HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB); /* 设置ETH TX DMA链表 */
  /* Initialize Rx Descriptors list: Chain Mode  */
  HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB); /* 设置ETH RX DMA链表 */
#if LWIP_ARP || LWIP_ETHERNET
  /* set MAC hardware address length */
  netif->hwaddr_len = ETH_HWADDR_LEN;
  /* set MAC hardware address */
  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 */
  /* Enable MAC and DMA transmission and reception */
  HAL_ETH_Start(&heth);  /* 开启ETH网 */
/* USER CODE BEGIN PHY_PRE_CONFIG */
/* USER CODE END PHY_PRE_CONFIG */
  /**** Configure PHY to generate an interrupt when Eth Link state changes ****/
  /* Read Register Configuration */
  HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, &regvalue);
  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, &regvalue);
  regvalue |= PHY_MISR_LINK_INT_EN;
  /* Enable Interrupt on change of link status */
  HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue);  /* 以上为配置eth寄存器,至于寄存器是干嘛的,可以看下上面DP83848小节 */
/* 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 */
}


使用特权

评论回复
12
晓伍|  楼主 | 2021-8-1 14:27 | 只看该作者
low_level_output是 lwip 往网卡驱动发送函数,代码如下:

此函数就是把lwip的pbuf数据copy到eth的tx dma中,然后发送

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;
    }
  /* Prepare transmit descriptors to give to DMA */
  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;
}


使用特权

评论回复
13
晓伍|  楼主 | 2021-8-1 14:27 | 只看该作者
low_level_input是lwip接受input数据,代码如下:

此函数就是把eth的rx dma中数据copy到pbuf中,然后交给lwip处理

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;
  /* get received frame */
  if (HAL_ETH_GetReceivedFrame(&heth) != HAL_OK)
    return NULL;
  /* Obtain the size of the packet and put it into the "len" variable. */
  len = heth.RxFrameInfos.length;
  buffer = (uint8_t *)heth.RxFrameInfos.buffer;
  if (len > 0)
  {
    /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  }
  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;
}


使用特权

评论回复
14
晓伍|  楼主 | 2021-8-1 14:28 | 只看该作者
3.代码简介(以CubeMx生成代码做说明)
我们来直接说下代码架构跟流程,具体细项不做说明



大概不的部分分为几个:

1)MX_LWIP_Init,包含lwip的初始化,网卡添加,设置默认网卡,启动网卡等

2)在main的while循环中判断是否有数据进来,如果有数据进来,那么通过ETH读出数据,叫给lwip处理


使用特权

评论回复
15
晓伍|  楼主 | 2021-8-1 14:30 | 只看该作者
六. 在STM32F407使用freertos移植lwip
在确定每个配置之前,建议别修改其他参数,因为我修改了head size不能正常PING通,这个我会在下面总结问题说明

1.Cubemx配置过程
其中RCC/SYS/UART/ETH/LWIP勾选跟noos完全一模一样,所以在这里我们就不做介绍了,我们主要介绍下勾选FreeRTOS以及上述勾选的差异点

1.1 勾选FreeRTOS
主要牵扯到两点,我用的CMSIS_V1,另外开启了浮点运算,防止有现成用到浮点后宕机情况



1.2 勾选FreeRTOS
SYS/ETH/LWIP的差异

SYS cubemx强烈建议别用systick,所以我们选择TIM1



ETH mode由轮询改为中断(只可选择中断)





LWIP我们改为有RTOS



使用特权

评论回复
16
晓伍|  楼主 | 2021-8-1 14:32 | 只看该作者
2.自己移植过程
LwIP 不仅能在裸机上运行,也能在操作系统环境下运行,而且在操作系统环境下,用户能使用NETCONN API 与 Socket API 编程,相比 RAW API 编程会更加简便。操作系统环境下,这意味着多线程环境,一般来说 LwIP 作为一个独立的处理线程运行,用户程序也独立为一个/多个线程,这样子在操作系统中就相互独立开,并且借助操作系统的 IPC 通信机制,更好地实现功能的需求。

LwIP 在设计之初,设计者无法预测 LwIP 运行的环境是怎么样的,而且世界上操作系统那么多,根本没法统一,而如果 LwIP 要运行在操作系统环境中,那么就必须产生依赖,即 LwIP 需要依赖操作系统自身的通信机制,如信号量、互斥量、消息队列(邮箱)等,所以 LwIP 设计者在设计的时候就提供一套与操作系统相关的接口,由用户根据操作系统的不同进行移植,这样子就能降低耦合度,让 LwIP 内核不受其运行的环境影响,因为往往用户并不能完全了解内核的运作,所以只需要用户在移植的时候对 LwIP 提供的接口根据不同操作系统进行完善即可。

2.1 移植FreeRTOS到工程
这个牵扯到RTOS的移植,不是本**范畴,可以自行百度,或者网络上现成的工程一堆,可以自己百度使用

2.2 配置lwip的宏文件lwipopts.h(可以直接把这个copy到你的rtos的lwip中)
#ifndef __LWIPOPTS__H__
#define __LWIPOPTS__H__
#include "main.h"
/*-----------------------------------------------------------------------------*/
/* Current version of LwIP supported by CubeMx: 2.1.2 -*/
/*-----------------------------------------------------------------------------*/
/* Within 'USER CODE' section, code will be kept by default at each generation */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#ifdef __cplusplus
extern "C" {
#endif
/* STM32CubeMX Specific Parameters (not defined in opt.h) ---------------------*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- WITH_RTOS enabled (Since FREERTOS is set) -----*/
#define WITH_RTOS 1  /* 带RTOS */
/*----- CHECKSUM_BY_HARDWARE enabled -----*/
#define CHECKSUM_BY_HARDWARE 1
/*-----------------------------------------------------------------------------*/
/* LwIP Stack Parameters (modified compared to initialization value in opt.h) -*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
#define MEM_ALIGNMENT 4
/*----- Value in opt.h for LWIP_ETHERNET: LWIP_ARP || PPPOE_SUPPORT -*/
#define LWIP_ETHERNET 1
/*----- Value in opt.h for LWIP_DNS_SECURE: (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT) -*/
#define LWIP_DNS_SECURE 7
/*----- Value in opt.h for TCP_SND_QUEUELEN: (4*TCP_SND_BUF + (TCP_MSS - 1))/TCP_MSS -----*/
#define TCP_SND_QUEUELEN 9
/*----- Value in opt.h for TCP_SNDLOWAT: LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -*/
#define TCP_SNDLOWAT 1071
/*----- Value in opt.h for TCP_SNDQUEUELOWAT: LWIP_MAX(TCP_SND_QUEUELEN)/2, 5) -*/
#define TCP_SNDQUEUELOWAT 5
/*----- Value in opt.h for TCP_WND_UPDATE_THRESHOLD: LWIP_MIN(TCP_WND/4, TCP_MSS*4) -----*/
#define TCP_WND_UPDATE_THRESHOLD 536
/*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
#define LWIP_NETIF_LINK_CALLBACK 1
/*----- Value in opt.h for TCPIP_THREAD_STACKSIZE: 0 -----*/
#define TCPIP_THREAD_STACKSIZE 1024 /* TCP/IP线程的栈大小为1024 */
/*----- Value in opt.h for TCPIP_THREAD_PRIO: 1 -----*/
#define TCPIP_THREAD_PRIO osPriorityNormal /* TCP/IP线程的优先级为normal */
/*----- Value in opt.h for TCPIP_MBOX_SIZE: 0 -----*/
#define TCPIP_MBOX_SIZE 6  /* 消息邮箱大小为6个 */
/*----- Value in opt.h for SLIPIF_THREAD_STACKSIZE: 0 -----*/
#define SLIPIF_THREAD_STACKSIZE 1024
/*----- Value in opt.h for SLIPIF_THREAD_PRIO: 1 -----*/
#define SLIPIF_THREAD_PRIO 3
/*----- Value in opt.h for DEFAULT_THREAD_STACKSIZE: 0 -----*/
#define DEFAULT_THREAD_STACKSIZE 1024
/*----- Value in opt.h for DEFAULT_THREAD_PRIO: 1 -----*/
#define DEFAULT_THREAD_PRIO 3
/*----- Value in opt.h for DEFAULT_UDP_RECVMBOX_SIZE: 0 -----*/
#define DEFAULT_UDP_RECVMBOX_SIZE 6
/*----- Value in opt.h for DEFAULT_TCP_RECVMBOX_SIZE: 0 -----*/
#define DEFAULT_TCP_RECVMBOX_SIZE 6
/*----- Value in opt.h for DEFAULT_ACCEPTMBOX_SIZE: 0 -----*/
#define DEFAULT_ACCEPTMBOX_SIZE 6
/*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
#define RECV_BUFSIZE_DEFAULT 2000000000
/*----- Value in opt.h for LWIP_STATS: 1 -----*/
#define LWIP_STATS 0
/*----- Value in opt.h for CHECKSUM_GEN_IP: 1 -----*/
#define CHECKSUM_GEN_IP 0
/*----- Value in opt.h for CHECKSUM_GEN_UDP: 1 -----*/
#define CHECKSUM_GEN_UDP 0
/*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/
#define CHECKSUM_GEN_TCP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP: 1 -----*/
#define CHECKSUM_GEN_ICMP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/
#define CHECKSUM_GEN_ICMP6 0
/*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/
#define CHECKSUM_CHECK_IP 0
/*----- Value in opt.h for CHECKSUM_CHECK_UDP: 1 -----*/
#define CHECKSUM_CHECK_UDP 0
/*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/
#define CHECKSUM_CHECK_TCP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP: 1 -----*/
#define CHECKSUM_CHECK_ICMP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/
#define CHECKSUM_CHECK_ICMP6 0
/*-----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
#ifdef __cplusplus
}
#endif
#endif /*__LWIPOPTS__H__ */


使用特权

评论回复
17
晓伍|  楼主 | 2021-8-1 14:33 | 只看该作者
2.3 sys_arch.c/h 文件的编写
操作系统环境下,LwIP 移植的核心就是编写与操作系统相关的接口文件 sys_arch.c 和 sys_arch.h,这两个文件可以自己创建也可以从 contrib 包中获取,路径分别为“contrib-2.1.0\ports\freertos”与“contrib-2.1.0\ports\freertos\includearch”,用户在移植的时候必须根据操作系统的功能为协议栈提供相应的接口,如邮箱(因为本次移植以 FreeRTOS 为例子, FreeRTOS 中没有邮箱这种概念,但是可以使用消息队列替代,为了迎合 LwIP 中的命名,下文统一采用邮箱表示)、信号量、互斥量等,这些 IPC 通信机制是保证内核与上层 API 接口通信的基本保障,也是内核实现管理的继承,同时在 sys.h 文件中声明了用户需要实现的所有函数框架,这些函数具体见表格





看到那么多函数,是不是头都大了,其实这些函数的实现都是很简单的,首先讲解一下邮箱函数的实现。在 LwIP 中,用户代码与协议栈内部之间是通过邮箱进行数据的交互的,邮箱本质上就是一个指向数据的指针,API 将指针传递给内核,内核通过这个指针访问数据,然后去处理,反之内核将数据传递给用户代码也是通过邮箱将一个指针进行传递。在操作系统环境下,LwIP 会作为一个线程运行,线程的名字叫tcpip_thread,在初始化 LwIP 的时候,内核就会自动创建这个线程,并且在线程运行的时候阻塞在邮箱上,等待数据进行处理,这个邮箱数据的来源可能在底层网卡接收到的数据或者上层应用程序的数据,总之,tcpip_thread线程在获取到邮箱中的数据时候,就会退出阻塞态,去处理数据,在处理完毕数据后又进入阻塞态中等待数据的到来,如此反复。信号量与互斥量的实现为内核提供同步与互斥的机制,比如当用户想要发送一个数据的时候,就会调用上层 API 接口,API 接口就会去先发送一个数据给内核去处理,然后尝试获取一个信号量,因为此时是没有信号量的,所以就会阻塞用户线程;内核在知道用户想要发送数据后,就会调用对应的网卡去发送数据,当数据发送完成后就释放一个信号量告知用户线程发送完成,这样子用户线程就得以继续执行。

所以这些函数的接口都必须由用户实现,下面我们先来看下sys_arch.h

#ifndef __SYS_ARCH_H__
#define __SYS_ARCH_H__
#include "lwip/opt.h"
#if (NO_SYS != 0)
#error "NO_SYS need to be set to 0 to use threaded API"
#endif
#include "cmsis_os.h"
#ifdef  __cplusplus
extern "C" {
#endif
#if (osCMSIS < 0x20000U)
#define SYS_MBOX_NULL (osMessageQId)0
#define SYS_SEM_NULL  (osSemaphoreId)0
typedef osSemaphoreId sys_sem_t;
typedef osSemaphoreId sys_mutex_t;
typedef osMessageQId  sys_mbox_t;
typedef osThreadId    sys_thread_t;
#else
#define SYS_MBOX_NULL (osMessageQueueId_t)0
#define SYS_SEM_NULL  (osSemaphoreId_t)0
typedef osSemaphoreId_t     sys_sem_t;
typedef osSemaphoreId_t     sys_mutex_t;
typedef osMessageQueueId_t  sys_mbox_t;
typedef osThreadId_t        sys_thread_t;
#endif
#ifdef  __cplusplus
}
#endif
#endif /* __SYS_ARCH_H__ */


使用特权

评论回复
18
晓伍|  楼主 | 2021-8-1 14:34 | 只看该作者
接下来我们来下sys_arch.c的实现

/* lwIP includes. */
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#if !NO_SYS
#include "cmsis_os.h"
#if defined(LWIP_PROVIDE_ERRNO)
int errno;
#endif
/*-----------------------------------------------------------------------------------*/
//  Creates an empty mailbox.
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
#if (osCMSIS < 0x20000U)
  osMessageQDef(QUEUE, size, void *);
  *mbox = osMessageCreate(osMessageQ(QUEUE), NULL);
#else
  *mbox = osMessageQueueNew(size, sizeof(void *), NULL);
#endif
#if SYS_STATS
  ++lwip_stats.sys.mbox.used;
  if(lwip_stats.sys.mbox.max < lwip_stats.sys.mbox.used)
  {
    lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
  }
#endif /* SYS_STATS */
  if(*mbox == NULL)
    return ERR_MEM;
  return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/*
  Deallocates a mailbox. If there are messages still present in the
  mailbox when the mailbox is deallocated, it is an indication of a
  programming error in lwIP and the developer should be notified.
*/
void sys_mbox_free(sys_mbox_t *mbox)
{
#if (osCMSIS < 0x20000U)
  if(osMessageWaiting(*mbox))
#else
  if(osMessageQueueGetCount(*mbox))
#endif
  {
    /* Line for breakpoint.  Should never break here! */
    portNOP();
#if SYS_STATS
    lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
  }
#if (osCMSIS < 0x20000U)
  osMessageDelete(*mbox);
#else
  osMessageQueueDelete(*mbox);
#endif
#if SYS_STATS
  --lwip_stats.sys.mbox.used;
#endif /* SYS_STATS */
}
/*-----------------------------------------------------------------------------------*/
//   Posts the "msg" to the mailbox.
void sys_mbox_post(sys_mbox_t *mbox, void *data)
{
#if (osCMSIS < 0x20000U)
  while(osMessagePut(*mbox, (uint32_t)data, osWaitForever) != osOK);
#else
  while(osMessageQueuePut(*mbox, &data, 0, osWaitForever) != osOK);
#endif
}
/*-----------------------------------------------------------------------------------*/
//   Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
  err_t result;
#if (osCMSIS < 0x20000U)
  if(osMessagePut(*mbox, (uint32_t)msg, 0) == osOK)
#else
  if(osMessageQueuePut(*mbox, &msg, 0, 0) == osOK)
#endif
  {
    result = ERR_OK;
  }
  else
  {
    // could not post, queue must be full
    result = ERR_MEM;
#if SYS_STATS
    lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
  }
  return result;
}
/*-----------------------------------------------------------------------------------*/
//   Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)
{
  return sys_mbox_trypost(mbox, msg);
}
/*-----------------------------------------------------------------------------------*/
/*
  Blocks the thread until a message arrives in the mailbox, but does
  not block the thread longer than "timeout" milliseconds (similar to
  the sys_arch_sem_wait() function). The "msg" argument is a result
  parameter that is set by the function (i.e., by doing "*msg =
  ptr"). The "msg" parameter maybe NULL to indicate that the message
  should be dropped.
  The return values are the same as for the sys_arch_sem_wait() function:
  Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a
  timeout.
  Note that a function with a similar name, sys_mbox_fetch(), is
  implemented by lwIP.
*/
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
#if (osCMSIS < 0x20000U)
  osEvent event;
  uint32_t starttime = osKernelSysTick();
#else
  osStatus_t status;
  uint32_t starttime = osKernelGetTickCount();
#endif
  if(timeout != 0)
  {
#if (osCMSIS < 0x20000U)
    event = osMessageGet (*mbox, timeout);
    if(event.status == osEventMessage)
    {
      *msg = (void *)event.value.v;
      return (osKernelSysTick() - starttime);
    }
#else
    status = osMessageQueueGet(*mbox, msg, 0, timeout);
    if (status == osOK)
    {
      return (osKernelGetTickCount() - starttime);
    }
#endif
    else
    {
      return SYS_ARCH_TIMEOUT;
    }
  }
  else
  {
#if (osCMSIS < 0x20000U)
    event = osMessageGet (*mbox, osWaitForever);
    *msg = (void *)event.value.v;
    return (osKernelSysTick() - starttime);
#else
    osMessageQueueGet(*mbox, msg, 0, osWaitForever );
    return (osKernelGetTickCount() - starttime);
#endif
  }
}
/*-----------------------------------------------------------------------------------*/
/*
  Similar to sys_arch_mbox_fetch, but if message is not ready immediately, we'll
  return with SYS_MBOX_EMPTY.  On success, 0 is returned.
*/
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
#if (osCMSIS < 0x20000U)
  osEvent event;
  event = osMessageGet (*mbox, 0);
  if(event.status == osEventMessage)
  {
    *msg = (void *)event.value.v;
#else
  if (osMessageQueueGet(*mbox, msg, 0, 0) == osOK)
  {
#endif
    return ERR_OK;
  }
  else
  {
    return SYS_MBOX_EMPTY;
  }
}
/*----------------------------------------------------------------------------------*/
int sys_mbox_valid(sys_mbox_t *mbox)
{
  if (*mbox == SYS_MBOX_NULL)
    return 0;
  else
    return 1;
}
/*-----------------------------------------------------------------------------------*/
void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
  *mbox = SYS_MBOX_NULL;
}
/*-----------------------------------------------------------------------------------*/
//  Creates a new semaphore. The "count" argument specifies
//  the initial state of the semaphore.
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
#if (osCMSIS < 0x20000U)
  osSemaphoreDef(SEM);
  *sem = osSemaphoreCreate (osSemaphore(SEM), 1);
#else
  *sem = osSemaphoreNew(UINT16_MAX, count, NULL);
#endif
  if(*sem == NULL)
  {
#if SYS_STATS
    ++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */
    return ERR_MEM;
  }
  if(count == 0)        // Means it can't be taken
  {
#if (osCMSIS < 0x20000U)
    osSemaphoreWait(*sem, 0);
#else
    osSemaphoreAcquire(*sem, 0);
#endif
  }
#if SYS_STATS
  ++lwip_stats.sys.sem.used;
  if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used) {
    lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
  }
#endif /* SYS_STATS */
  return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/*
  Blocks the thread while waiting for the semaphore to be
  signaled. If the "timeout" argument is non-zero, the thread should
  only be blocked for the specified time (measured in
  milliseconds).
  If the timeout argument is non-zero, the return value is the number of
  milliseconds spent waiting for the semaphore to be signaled. If the
  semaphore wasn't signaled within the specified time, the return value is
  SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
  (i.e., it was already signaled), the function may return zero.
  Notice that lwIP implements a function with a similar name,
  sys_sem_wait(), that uses the sys_arch_sem_wait() function.
*/
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
#if (osCMSIS < 0x20000U)
  uint32_t starttime = osKernelSysTick();
#else
  uint32_t starttime = osKernelGetTickCount();
#endif
  if(timeout != 0)
  {
#if (osCMSIS < 0x20000U)
    if(osSemaphoreWait (*sem, timeout) == osOK)
    {
      return (osKernelSysTick() - starttime);
#else
    if(osSemaphoreAcquire(*sem, timeout) == osOK)
    {
        return (osKernelGetTickCount() - starttime);
#endif
    }
    else
    {
      return SYS_ARCH_TIMEOUT;
    }
  }
  else
  {
#if (osCMSIS < 0x20000U)
    while(osSemaphoreWait (*sem, osWaitForever) != osOK);
    return (osKernelSysTick() - starttime);
#else
    while(osSemaphoreAcquire(*sem, osWaitForever) != osOK);
    return (osKernelGetTickCount() - starttime);
#endif
  }
}
/*-----------------------------------------------------------------------------------*/
// Signals a semaphore
void sys_sem_signal(sys_sem_t *sem)
{
  osSemaphoreRelease(*sem);
}
/*-----------------------------------------------------------------------------------*/
// Deallocates a semaphore
void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
  --lwip_stats.sys.sem.used;
#endif /* SYS_STATS */
  osSemaphoreDelete(*sem);
}
/*-----------------------------------------------------------------------------------*/
int sys_sem_valid(sys_sem_t *sem)
{
  if (*sem == SYS_SEM_NULL)
    return 0;
  else
    return 1;
}
/*-----------------------------------------------------------------------------------*/
void sys_sem_set_invalid(sys_sem_t *sem)
{
  *sem = SYS_SEM_NULL;
}
/*-----------------------------------------------------------------------------------*/
#if (osCMSIS < 0x20000U)
osMutexId lwip_sys_mutex;
osMutexDef(lwip_sys_mutex);
#else
osMutexId_t lwip_sys_mutex;
#endif
// Initialize sys arch
void sys_init(void)
{
#if (osCMSIS < 0x20000U)
  lwip_sys_mutex = osMutexCreate(osMutex(lwip_sys_mutex));
#else
  lwip_sys_mutex = osMutexNew(NULL);
#endif
}
/*-----------------------------------------------------------------------------------*/
                                      /* Mutexes*/
/*-----------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------*/
#if LWIP_COMPAT_MUTEX == 0
/* Create a new mutex*/
err_t sys_mutex_new(sys_mutex_t *mutex) {
#if (osCMSIS < 0x20000U)
  osMutexDef(MUTEX);
  *mutex = osMutexCreate(osMutex(MUTEX));
#else
  *mutex = osMutexNew(NULL);
#endif
  if(*mutex == NULL)
  {
#if SYS_STATS
    ++lwip_stats.sys.mutex.err;
#endif /* SYS_STATS */
    return ERR_MEM;
  }
#if SYS_STATS
  ++lwip_stats.sys.mutex.used;
  if (lwip_stats.sys.mutex.max < lwip_stats.sys.mutex.used) {
    lwip_stats.sys.mutex.max = lwip_stats.sys.mutex.used;
  }
#endif /* SYS_STATS */
  return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/* Deallocate a mutex*/
void sys_mutex_free(sys_mutex_t *mutex)
{
#if SYS_STATS
      --lwip_stats.sys.mutex.used;
#endif /* SYS_STATS */
  osMutexDelete(*mutex);
}
/*-----------------------------------------------------------------------------------*/
/* Lock a mutex*/
void sys_mutex_lock(sys_mutex_t *mutex)
{
#if (osCMSIS < 0x20000U)
  osMutexWait(*mutex, osWaitForever);
#else
  osMutexAcquire(*mutex, osWaitForever);
#endif
}
/*-----------------------------------------------------------------------------------*/
/* Unlock a mutex*/
void sys_mutex_unlock(sys_mutex_t *mutex)
{
  osMutexRelease(*mutex);
}
#endif /*LWIP_COMPAT_MUTEX*/
/*-----------------------------------------------------------------------------------*/
// TODO
/*-----------------------------------------------------------------------------------*/
/*
  Starts a new thread with priority "prio" that will begin its execution in the
  function "thread()". The "arg" argument will be passed as an argument to the
  thread() function. The id of the new thread is returned. Both the id and
  the priority are system dependent.
*/
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread , void *arg, int stacksize, int prio)
{
#if (osCMSIS < 0x20000U)
  const osThreadDef_t os_thread_def = { (char *)name, (os_pthread)thread, (osPriority)prio, 0, stacksize};
  return osThreadCreate(&os_thread_def, arg);
#else
  const osThreadAttr_t attributes = {
                        .name = name,
                        .stack_size = stacksize,
                        .priority = (osPriority_t)prio,
                      };
  return osThreadNew(thread, arg, &attributes);
#endif
}
/*
  This optional function does a "fast" critical region protection and returns
  the previous protection level. This function is only called during very short
  critical regions. An embedded system which supports ISR-based drivers might
  want to implement this function by disabling interrupts. Task-based systems
  might want to implement this by using a mutex or disabling tasking. This
  function should support recursive calls from the same task or interrupt. In
  other words, sys_arch_protect() could be called while already protected. In
  that case the return value indicates that it is already protected.
  sys_arch_protect() is only required if your port is supporting an operating
  system.
  Note: This function is based on FreeRTOS API, because no equivalent CMSIS-RTOS
        API is available
*/
sys_prot_t sys_arch_protect(void)
{
#if (osCMSIS < 0x20000U)
  osMutexWait(lwip_sys_mutex, osWaitForever);
#else
  osMutexAcquire(lwip_sys_mutex, osWaitForever);
#endif
  return (sys_prot_t)1;
}
/*
  This optional function does a "fast" set of critical region protection to the
  value specified by pval. See the documentation for sys_arch_protect() for
  more information. This function is only required if your port is supporting
  an operating system.
  Note: This function is based on FreeRTOS API, because no equivalent CMSIS-RTOS
        API is available
*/
void sys_arch_unprotect(sys_prot_t pval)
{
  ( void ) pval;
  osMutexRelease(lwip_sys_mutex);
}
#endif /* !NO_SYS */


使用特权

评论回复
19
晓伍|  楼主 | 2021-8-1 14:35 | 只看该作者
2.4 网卡底层的编写
在无操作性移植的时候,我们的网卡收发数据就是单纯的收发数据, 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
      {
        LOCK_TCPIP_CORE();
        p = low_level_input( netif );
        if   (p != NULL)
        {
          if (netif->input( p, netif) != ERR_OK )
          {
            pbuf_free(p);
          }
        }
        UNLOCK_TCPIP_CORE();
      } while(p!=NULL);
    }
  }
}
在网卡接收线程中需要留意一下以下内容:网卡接收线程是需要通过信号量机制去接收数据的,一般来说我们都是使用中断的方式去获取网络数据包,当产生中断的时候,我们一般不会在中断中处理数据,而是告诉对应的线程去处理,也就是我们的网卡接收线程去处理数据,那么就会通过信号量进行同步,当网卡接收到了数据就会产生中断释放一个信号量,然后线程从阻塞中恢复,去获取网卡的数据并且向上层递交。当然我们还需要在中断中对网卡底层进行编写!

void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
  osSemaphoreRelease(s_xSemaphore);
}
这个函数是有ETH_IRQHandler中的HAL_ETH_IRQHandler调用!

网卡初始化函数 low_level_init()

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.Speed = ETH_SPEED_100M;
  heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
  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)
  {
    /* Set netif link flag */
    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
  /* set MAC hardware address length */
  netif->hwaddr_len = ETH_HWADDR_LEN;
  /* set MAC hardware address */
  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 */
/* create a binary semaphore used for informing ethernetif of frame reception */
  osSemaphoreDef(SEM);
  s_xSemaphore = osSemaphoreCreate(osSemaphore(SEM), 1);
/* create the task that handles the ETH_MAC */
/* USER CODE BEGIN OS_THREAD_DEF_CREATE_CMSIS_RTOS_V1 */
  osThreadDef(EthIf, ethernetif_input, osPriorityRealtime, 0, INTERFACE_THREAD_STACK_SIZE);
  osThreadCreate (osThread(EthIf), netif);
/* USER CODE END OS_THREAD_DEF_CREATE_CMSIS_RTOS_V1 */
  /* Enable MAC and DMA transmission and reception */
  HAL_ETH_Start(&heth);
/* USER CODE BEGIN PHY_PRE_CONFIG */
/* USER CODE END PHY_PRE_CONFIG */
  /**** Configure PHY to generate an interrupt when Eth Link state changes ****/
  /* Read Register Configuration */
  HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, &regvalue);
  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, &regvalue);
  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 */
}

其中low_level_output/low_level_input跟noos函数编写一致即可


使用特权

评论回复
20
晓伍|  楼主 | 2021-8-1 14:36 | 只看该作者
3.代码简介(以CubeMx生成代码做说明)

我大概分析了一下init以及收数据的代码流程(红线)


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

4113

帖子

1

粉丝