超详细 STM32H723 基于 DP83848 的网络通信系统设计:LWIP、RT-Thread(FreeRTOS)、STM32CubeMX 和 Keil MDK

工程环境:

MCU:STM32H723ZGT

ETH PHY :DP83848

RT-Thread:RT-Thread nano 3.1.5

Software Pack:STM32CubeH7 Firmware Package V1.10.0 / 11-February-2022

参考文章:

STM32H723配置以太网+Freertos注意事项

STM32H723+Lwip+ETH+CUBE 完整配置(排了巨多坑!)

Cube配置STM32H743+DP83848以太网工程

STM32H743+CubeMX-梳理MPU的设置

前言:

首先使用CubeMX配置相关外设和软件代码,导出Keil MDK工程,然后在Keil中修改相关代码。

内存规划:

D2域SRAM1(0x30000000-0x30000200 , 512B)存放ETH DMA的Rx描述符

D2域SRAM1(0x30000200-0x30000400 , 512B)存放ETH DMA的Tx描述符

D2域SRAM1(0x30000400-0x30004000 , 15KB)存放LWIP的RX_POOL内存池

D2域SRAM2(0x30004000-0x30008000 , 16KB)存放LWIP的内存堆

这里LWIP的RX_POOL内存池和内存堆不只局限于D2域,也可以放在D1域的AXI SRAM,AXI SRAM默认有320KB,大很多,但是不能放在DTCM中,因为ETH DMA无法访问DTCM。

注意:ETH DMA的Rx描述符和Tx描述符只能放在D2域。

我们这里把内存池和内存堆独立配置,LWIP还支持将内存池由内存堆实现,将内存堆由内存池实现。

一、CubeMX配置:

ETH外设配置:

选择ETH外设;

我这里使用的是MII接口,请根据实际情况选择;

配置ETH外设的MAC地址和ETH DMA描述符;

Ethernet MAC Address  :配置MAC地址;

Tx Descriptor Length  :ETH DMA Tx描述符长度,这里默认4个,后期可以在代码中修改;

First Tx Descriptor Address  :首个ETH DMA Tx描述符地址,这个地址必须是D2域的SRAM地址,这里配置为0x30000200

Rx Descriptor Length  :ETH DMA Rx描述符长度,这里默认4个,后期可以在代码中修改;

First Rx Descriptor Address  :首个ETH DMA Rx描述符地址,这个地址必须是D2域的SRAM地址,这里配置为0x30000000

Rx Buffers Length:Rx缓冲区长度,配置为1528

ETH DMA的描述符1个是24字节,这里分别把TX和RX配置为4个,也就是分别用了96字节,而我们分配的空间分别是512字节,最多可以配置512/4=21个,实际用不了这么多,剩余的空间就预留后期拓展吧,在配置MPU的时候要把这1KB(0x30000000-0x30000400 )单独保护起来。

开启ETH全局中断 

 根据实际情况配置以太网外设的GPIO,并将所有使用的GPIO配置为高速;

添加PHY芯片的复位引脚,我这里用的是PF13,将PF13配置为上拉推挽输出模式,默认输出高电平。

 MPU配置

D2域SRAM主要用于LWIP的RX_POOL内存池和内存堆,存放以太网接收和发送的数据,这里不缓存这个区域;

 D2域的0x30000000-0x30000400 这1KB用于存放ETH DMA的描述符,这里配置为共享设备模式。

 RTOS配置

STM32CubeMX也支持添加RT-Thread,需要手动安装软件包,但是STM32CubeMX不会生成LWIP与操作系统相关的代码,主要就是线程、信号量、邮箱、互斥锁等相关的代码,在这里我们先启用STM32CubeMX里的FreeRTOS操作系统,导出工程文件后在Keil里移除FreeRTOS,然后添加RT-Thread,并将LWIP与操作系统相关的代码替换成RT-Thread的代码。

LWIP配置

这里把LWIP的内存堆放在D2域的SRAM2中(0x30004000-0x30008000),LWIP的可用内存堆大小MEM_SIZE配置为14KB。

LWIP的内存堆也可以放在D1域的AXI SRAM中,但是不能放在DTCM中,因为ETH DMA无法访问DTCM。

配置TCPIP线程:

 将TCPIP线程的优先级设高点,在RT-Thread中数字越小优先级越高。

 配置IP地址:

选择PHY芯片:

这里只能选择LAN8742,由于我们用的是DP83848,后面我们在代码中修改一下PHY的地址。

串口配置

这里开启USART1用于输出调试信息,根据实际情况配置。

HAL库时钟配置

默认使用系统定时器SysTick

RCC时钟配置

启用外部HSE时钟

 时钟树根据实际使用情况配置

导出Keil工程

这里选择仅拷贝使用的库代码,将每个外设初始化代码放在单个文件中,开启修改备份。

二、 Keil代码修改

修改PHY地址寄存器:

DP83848的PHY地址是在PHYCR(0x19)这个寄存器中,打开lan8742.c文件,找到int32_t LAN8742_Init(lan8742_Object_t *pObj)函数,将图中第107行代码这里改成0x19。程序中是通过循环读取PHYCR(0x19)寄存器,直到读出的地址与addr匹配为止。

修改获取链路状态函数

LAN8742的链路状态是在地址为31的寄存器中,而DP83848在PHYSTS寄存器(地址0x10)中,

所以需要修改 LAN8742_GetLinkState() 函数,

 修改后如下:

/**
  * @brief  Get the link state of LAN8742 device.
  * @param  pObj: Pointer to device object. 
  * @param  pLinkState: Pointer to link state
  * @retval LAN8742_STATUS_LINK_DOWN  if link is down
  *         LAN8742_STATUS_AUTONEGO_NOTDONE if Auto nego not completed 
  *         LAN8742_STATUS_100MBITS_FULLDUPLEX if 100Mb/s FD
  *         LAN8742_STATUS_100MBITS_HALFDUPLEX if 100Mb/s HD
  *         LAN8742_STATUS_10MBITS_FULLDUPLEX  if 10Mb/s FD
  *         LAN8742_STATUS_10MBITS_HALFDUPLEX  if 10Mb/s HD       
  *         LAN8742_STATUS_READ_ERROR if connot read register
  *         LAN8742_STATUS_WRITE_ERROR if connot write to register
  */
int32_t LAN8742_GetLinkState(lan8742_Object_t *pObj)
{
  uint32_t readval = 0;
  
  /* Read Status register */
  if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BSR, &readval) < 0)
  {
    return LAN8742_STATUS_READ_ERROR;
  }
  
  /* Read Status register again */
  if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BSR, &readval) < 0)
  {
    return LAN8742_STATUS_READ_ERROR;
  }
  
  if((readval & LAN8742_BSR_LINK_STATUS) == 0)
  {
    /* Return Link Down status */
    return LAN8742_STATUS_LINK_DOWN;    
  }
  
  /* Check Auto negotiaition */
  if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &readval) < 0)//读取基本控制寄存器0x00
  {
    return LAN8742_STATUS_READ_ERROR;
  }
  
  if((readval & LAN8742_BCR_AUTONEGO_EN) != LAN8742_BCR_AUTONEGO_EN) //未启用自动协商
  {
    if(((readval & LAN8742_BCR_SPEED_SELECT) == LAN8742_BCR_SPEED_SELECT) && ((readval & LAN8742_BCR_DUPLEX_MODE) == LAN8742_BCR_DUPLEX_MODE)) 
    {
      return LAN8742_STATUS_100MBITS_FULLDUPLEX;
    }
    else if ((readval & LAN8742_BCR_SPEED_SELECT) == LAN8742_BCR_SPEED_SELECT)
    {
      return LAN8742_STATUS_100MBITS_HALFDUPLEX;
    }        
    else if ((readval & LAN8742_BCR_DUPLEX_MODE) == LAN8742_BCR_DUPLEX_MODE)
    {
      return LAN8742_STATUS_10MBITS_FULLDUPLEX;
    }
    else
    {
      return LAN8742_STATUS_10MBITS_HALFDUPLEX;
    }  		
  }
  else /* Auto Nego enabled */
  {
    //DP83848
    if(pObj->IO.ReadReg(pObj->DevAddr, 0x10, &readval) < 0) //DP83848,PHY状态寄存器(PHYSTS) 地址0x10
    {
      return LAN8742_STATUS_READ_ERROR;
    }
      
    /* Check if auto nego not done */
    if((readval & 0x0010) == 0) //bit4, Auto-Negotiation Complete
    {
      return LAN8742_STATUS_AUTONEGO_NOTDONE;//自动协商未完成
    }
    
    if((readval & 0x0006) == 0x04)//bit2,bit1,  100 Mb/s mode,Full duplex mode
    {
      return LAN8742_STATUS_100MBITS_FULLDUPLEX;
    }
    else if ((readval & 0x0006) == 0x00)//bit2,bit1,  100 Mb/s mode,Half duplex mode.
    {
      return LAN8742_STATUS_100MBITS_HALFDUPLEX;
    }
    else if ((readval & 0x0006) == 0x06)//bit2,bit1,  10 Mb/s mode,Half duplex mode.
    {
      return LAN8742_STATUS_10MBITS_FULLDUPLEX;
    }
    else
    {
      return LAN8742_STATUS_10MBITS_HALFDUPLEX; //10 Mb/s mode,Half duplex mode.
    }				
  }
}

添加PHY芯片复位代码:

打开ethernetif.c文件,找到static void low_level_init(struct netif *netif)函数,在这里添加PHY复位代码,我这里用的是PF13,根据实际情况修改。

添加LWIP的RX_POOL内存池定位代码

打开cc.h文件,

注释这行代码,防止编译时报错。

在文件末尾添加如下代码,将memp_memory_RX_POOL_base这个数组定位到D2域SRAM1中,由于前1KB用于存放ETH DMA描述符了,所以从0x30000400开始,一共15KB。

/* USER CODE BEGIN 0 */                                    
#if defined ( __ICCARM__ ) /*!< IAR Compiler */
#pragma location = 0x30000400
extern unsigned char memp_memory_RX_POOL_base[];

#elif defined ( __CC_ARM )  /* MDK ARM Compiler */
__attribute__((at(0x30000400))) extern unsigned char memp_memory_RX_POOL_base[];

#elif defined ( __GNUC__ ) /* GNU Compiler */
extern unsigned char memp_memory_RX_POOL_base[] __attribute__((section(".Rx_PoolSection")));
#endif 
/* USER CODE END 0 */

如果想要把RX_POOL内存池放到别的地方,如果用的是AC5编译器,则修改上图中第94行代码,如果用的是AC6编译器,则需要修改分散加载文件。

AC6编译器修改方式如下:

 

 分散加载文件内容如下:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00100000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00100000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00020000  {  ; RW data
   .ANY (+RW +ZI)
  }
  RW_IRAM2 0x24000000 0x00050000  {
   .ANY (+RW +ZI)
  }
  
  RW_IRAM3 0x30000000 0x00000200  {  ; 512B - SRAM1 存放ETH DMA RX描述符
   .ANY(.RxDecripSection)
  }
  
  RW_IRAM4 0x30000200 0x00000200  {  ; 512B - SRAM1 存放ETH DMA TX描述符
   .ANY(.TxDecripSection)
  }
  
  RW_IRAM5 0x30000400 0x00003C00  {  ; 15KB - SRAM1 存放RX_POOL内存池
   .ANY(.Rx_PoolSection)
  }
}

由于这里配置的RX_POOL内存池的大小只有15KB,还需要修改 ethernetif.c 文件中ETH_RX_BUFFER_CNT 宏的大小,15KB大约能分配9个ETH_RX_BUFFER 。

#define ETH_RX_BUFFER_CNT             9U

 

此时编译整个工程,下载程序到板子就能ping通了。

到此整个系统可以工作了,下面是将FreeRTOS替换成RT-Thread


三、移除FreeRTOS代码

 将FreeRTOS的文件包含目录删除

Ctrl+F 查找 cmsis_os.h,凡是包含了cmsis_os.h这个头文件的文件,全部注释掉

 

四、添加RT-Thread

 为了省事,这里直接使用Keil的包管理器(需要先安装RT-Thread Pack),添加RT-Thread,

 

打开board.c,添加头文件和外部函数声明,

 

#include "main.h"

extern HAL_StatusTypeDef HAL_Init(void);
extern void SystemClock_Config(void);

 在void rt_hw_board_init(void)函数中添加HAL库初始化和时钟配置的函数,这里使用SysTick定时器为RT-Thread提供心跳,

void rt_hw_board_init(void)
{
//#error "TODO 1: OS Tick Configuration."
    /* 
     * TODO 1: OS Tick Configuration
     * Enable the hardware timer and call the rt_os_tick_callback function
     * periodically with the frequency RT_TICK_PER_SECOND. 
     */
    HAL_Init();
    SystemClock_Config();
    SystemCoreClockUpdate();
    
    HAL_SYSTICK_Config(SystemCoreClock / RT_TICK_PER_SECOND);
    
    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}

 添加SysTick系统定时器中断函数,用于操作系统

/**
 * This is the timer interrupt service routine.
 *
 */
void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    HAL_IncTick();
    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}

修改 stm32h7xx_it.c 文件,注释图中的代码

 HardFault_Handler(void)和MemManage_Handler(void)函数RT-Thread已经实现了,这里注释掉

注释系统定时器中断函数,(已经在board.c文件中了)

 

打开 rt_config.h 文件,使能信号量、互斥锁、邮箱

把main线程的栈改大点

 

 新建一个 board.h 文件,放到工程目录里,这里面主要是芯片存储有关的内容

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-11-5      SummerGift   first version
 */

#ifndef __BOARD_H__
#define __BOARD_H__

#include <rtthread.h>
#include <stm32h7xx.h>
//#include "drv_common.h"
//#include "drv_gpio.h"

#ifdef __cplusplus
extern "C" {
#endif

#define STM32_FLASH_START_ADRESS     ((uint32_t)0x08000000)
#define STM32_FLASH_SIZE             (1024 * 1024)
#define STM32_FLASH_END_ADDRESS      ((uint32_t)(STM32_FLASH_START_ADRESS + STM32_FLASH_SIZE))

#define STM32_SRAM_SIZE           (256) 
#define STM32_SRAM_END            (0x24000000 + STM32_SRAM_SIZE * 1024) /*AXI SRAM, 256KB*/

#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int Image$$RW_IRAM1$$ZI$$Limit;
#define HEAP_BEGIN      (&Image$$RW_IRAM1$$ZI$$Limit)
#elif __ICCARM__
#pragma section="CSTACK"
#define HEAP_BEGIN      (__segment_end("CSTACK"))
#else
extern int __bss_end;
#define HEAP_BEGIN      (&__bss_end)
#endif

#define HEAP_END        STM32_SRAM_END

void SystemClock_Config(void);

#ifdef __cplusplus
}
#endif

#endif

五、替换sys_arch.c、sys_arch.h、ethernetif.c中有关操作系统的代码

修改 sys_arch.h 文件,如下:

#ifndef __SYS_ARCH_H__
#define __SYS_ARCH_H__

#include "lwip/opt.h"

#include "arch/cc.h"
#include <rtthread.h>

#if (NO_SYS != 0)
#error "NO_SYS need to be set to 0 to use threaded API"
#endif

#ifdef  __cplusplus
extern "C" {
#endif

#ifndef BYTE_ORDER
#define BYTE_ORDER LITTLE_ENDIAN
#endif

#define SYS_MBOX_NULL RT_NULL
#define SYS_SEM_NULL  RT_NULL

//typedef uint32_t sys_prot_t;

#define SYS_MBOX_SIZE 10
#define SYS_LWIP_TIMER_NAME "lwip_timer"
#define SYS_LWIP_MBOX_NAME "lwip_mbox"
#define SYS_LWIP_SEM_NAME "lwip_sem"
#define SYS_LWIP_MUTEX_NAME "lwip_mu"

typedef rt_sem_t sys_sem_t;
typedef rt_mutex_t sys_mutex_t;
typedef rt_mailbox_t  sys_mbox_t;
typedef rt_thread_t sys_thread_t;

#ifdef  __cplusplus
}
#endif

#endif /* __SYS_ARCH_H__ */

修改 sys_arch.c 文件,这个文件里面是有关线程、信号量等的操作,修改成RT-Thread的实现方式:

/* 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 <rthw.h>
#include <rtthread.h>
#include "sys_arch.h"
/* ====================== Mailbox ====================== */

/*
 * Create an empty mailbox for maximum "size" elements
 *
 * @return the operation status, ERR_OK on OK; others on error
 */
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
    static unsigned short counter = 0;
    char tname[RT_NAME_MAX];
    sys_mbox_t tmpmbox;

    RT_DEBUG_NOT_IN_INTERRUPT;

    rt_snprintf(tname, RT_NAME_MAX, "%s%d", SYS_LWIP_MBOX_NAME, counter);
    counter ++;

    tmpmbox = rt_mb_create(tname, size, RT_IPC_FLAG_FIFO);
    if (tmpmbox != RT_NULL)
    {
        *mbox = tmpmbox;

        return ERR_OK;
    }

    return ERR_MEM;
}

/*
 * Deallocate a mailbox
 */
void sys_mbox_free(sys_mbox_t *mbox)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

    rt_mb_delete(*mbox);

    return;
}

/** Post a message to an mbox - may not fail
 * -> blocks if full, only used from tasks not from ISR
 * @param mbox mbox to posts the message
 * @param msg message to post (ATTENTION: can be NULL)
 */
void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

    rt_mb_send_wait(*mbox, (rt_uint32_t)msg, RT_WAITING_FOREVER);

    return;
}

/*
 * Try to post the "msg" to the mailbox
 *
 * @return return ERR_OK if the "msg" is posted, ERR_MEM if the mailbox is full
 */
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
    if (rt_mb_send(*mbox, (rt_uint32_t)msg) == RT_EOK)
        return ERR_OK;

    return ERR_MEM;
}

err_t
sys_mbox_trypost_fromisr(sys_mbox_t *q, void *msg)
{
  return sys_mbox_trypost(q, msg);
}

/** Wait for a new message to arrive in the mbox
 * @param mbox mbox to get a message from
 * @param msg pointer where the message is stored
 * @param timeout maximum time (in milliseconds) to wait for a message
 * @return time (in milliseconds) waited for a message, may be 0 if not waited
           or SYS_ARCH_TIMEOUT on timeout
 *         The returned time has to be accurate to prevent timer jitter!
 */
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
    rt_err_t ret;
    s32_t t;
    u32_t tick;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* get the begin tick */
    tick = rt_tick_get();

    if(timeout == 0)
        t = RT_WAITING_FOREVER;
    else
    {
        /* convirt msecond to os tick */
        if (timeout < (1000/RT_TICK_PER_SECOND))
            t = 1;
        else
            t = timeout / (1000/RT_TICK_PER_SECOND);
    }

    ret = rt_mb_recv(*mbox, (rt_ubase_t *)msg, t);
    if(ret != RT_EOK)
    {
        return SYS_ARCH_TIMEOUT;
    }

    /* get elapse msecond */
    tick = rt_tick_get() - tick;

    /* convert tick to msecond */
    tick = tick * (1000 / RT_TICK_PER_SECOND);
    if (tick == 0)
        tick = 1;

    return tick;
}

/** Wait for a new message to arrive in the mbox
 * @param mbox mbox to get a message from
 * @param msg pointer where the message is stored
 * @param timeout maximum time (in milliseconds) to wait for a message
 * @return 0 (milliseconds) if a message has been received
 *         or SYS_MBOX_EMPTY if the mailbox is empty
 */
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
    int ret;

    ret = rt_mb_recv(*mbox, (rt_ubase_t *)msg, 0);

    if(ret == -RT_ETIMEOUT)
        return SYS_ARCH_TIMEOUT;
    else
    {
        if (ret == RT_EOK)
            ret = 1;
    }

    return ret;
}

#ifndef sys_mbox_valid
/** Check if an mbox is valid/allocated:
 *  return 1 for valid, 0 for invalid
 */
int sys_mbox_valid(sys_mbox_t *mbox)
{
    return (int)(*mbox);
}
#endif

#ifndef sys_mbox_set_invalid
/** Set an mbox invalid so that sys_mbox_valid returns 0
 */
void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
    *mbox = RT_NULL;
}
#endif
/*-----------------------------------------------------------------------------------*/
/*
 * Create a new semaphore
 *
 * @return the operation status, ERR_OK on OK; others on error
 */
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
    static unsigned short counter = 0;
    char tname[RT_NAME_MAX];
    sys_sem_t tmpsem;

    RT_DEBUG_NOT_IN_INTERRUPT;

    rt_snprintf(tname, RT_NAME_MAX, "%s%d", SYS_LWIP_SEM_NAME, counter);
    counter ++;

    tmpsem = rt_sem_create(tname, count, RT_IPC_FLAG_FIFO);
    if (tmpsem == RT_NULL)
        return ERR_MEM;
    else
    {
        *sem = tmpsem;

        return ERR_OK;
    }
}
/*-----------------------------------------------------------------------------------*/
/*
 * Block the thread while waiting for the semaphore to be signaled
 *
 * @return If the timeout argument is non-zero, it will return the number of milliseconds
 *         spent waiting for the semaphore to be signaled; If the semaphore isn't signaled
 *         within the specified time, it will return SYS_ARCH_TIMEOUT; If the thread doesn't
 *         wait for the semaphore, it will return zero
 */
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
    rt_err_t ret;
    s32_t t;
    u32_t tick;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* get the begin tick */
    tick = rt_tick_get();
    if (timeout == 0)
        t = RT_WAITING_FOREVER;
    else
    {
        /* convert msecond to os tick */
        if (timeout < (1000/RT_TICK_PER_SECOND))
            t = 1;
        else
            t = timeout / (1000/RT_TICK_PER_SECOND);
    }

    ret = rt_sem_take(*sem, t);

    if (ret == -RT_ETIMEOUT)
        return SYS_ARCH_TIMEOUT;
    else
    {
        if (ret == RT_EOK)
            ret = 1;
    }

    /* get elapse msecond */
    tick = rt_tick_get() - tick;

    /* convert tick to msecond */
    tick = tick * (1000 / RT_TICK_PER_SECOND);
    if (tick == 0)
        tick = 1;

    return tick;
}

/*
 * Signal a semaphore
 */
void sys_sem_signal(sys_sem_t *sem)
{
    rt_sem_release(*sem);
}

/*
 * Deallocate a semaphore
 */
void sys_sem_free(sys_sem_t *sem)
{
    RT_DEBUG_NOT_IN_INTERRUPT;
    rt_sem_delete(*sem);
}

#ifndef sys_sem_valid
/** Check if a semaphore is valid/allocated:
 *  return 1 for valid, 0 for invalid
 */
int sys_sem_valid(sys_sem_t *sem)
{
    return (int)(*sem);
}
#endif

#ifndef sys_sem_set_invalid
/** Set a semaphore invalid so that sys_sem_valid returns 0
 */
void sys_sem_set_invalid(sys_sem_t *sem)
{
    *sem = RT_NULL;
}
#endif

/* ====================== Mutex ====================== */

/** Create a new mutex
 * @param mutex pointer to the mutex to create
 * @return a new mutex
 */
err_t sys_mutex_new(sys_mutex_t *mutex)
{
    static unsigned short counter = 0;
    char tname[RT_NAME_MAX];
    sys_mutex_t tmpmutex;

    RT_DEBUG_NOT_IN_INTERRUPT;

    rt_snprintf(tname, RT_NAME_MAX, "%s%d", SYS_LWIP_MUTEX_NAME, counter);
    counter ++;

    tmpmutex = rt_mutex_create(tname, RT_IPC_FLAG_FIFO);
    if (tmpmutex == RT_NULL)
        return ERR_MEM;
    else
    {
        *mutex = tmpmutex;

        return ERR_OK;
    }
}

/** Lock a mutex
 * @param mutex the mutex to lock
 */
void sys_mutex_lock(sys_mutex_t *mutex)
{
    RT_DEBUG_NOT_IN_INTERRUPT;
    rt_mutex_take(*mutex, RT_WAITING_FOREVER);

    return;
}

/** Unlock a mutex
 * @param mutex the mutex to unlock
 */
void sys_mutex_unlock(sys_mutex_t *mutex)
{
    rt_mutex_release(*mutex);
}

/** Delete a semaphore
 * @param mutex the mutex to delete
 */
void sys_mutex_free(sys_mutex_t *mutex)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

    rt_mutex_delete(*mutex);
}

#ifndef sys_mutex_valid
/** Check if a mutex is valid/allocated:
 *  return 1 for valid, 0 for invalid
 */
int sys_mutex_valid(sys_mutex_t *mutex)
{
    return (int)(*mutex);
}
#endif

#ifndef sys_mutex_set_invalid
/** Set a mutex invalid so that sys_mutex_valid returns 0
 */
void sys_mutex_set_invalid(sys_mutex_t *mutex)
{
    *mutex = RT_NULL;
}
#endif

// Initialize sys arch
void sys_init(void)
{

}

/* ====================== System ====================== */

/*
 * Start a new thread named "name" 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
 */
sys_thread_t sys_thread_new(const char    *name,
                            lwip_thread_fn thread,
                            void          *arg,
                            int            stacksize,
                            int            prio)
{
    rt_thread_t t;

    RT_DEBUG_NOT_IN_INTERRUPT;
    
    /* create thread */
    t = rt_thread_create(name, thread, arg, stacksize, prio, 20);
    RT_ASSERT(t != RT_NULL);
    
    /* startup thread */
    rt_thread_startup(t);
    
    return t;
}

sys_prot_t sys_arch_protect(void)
{
    rt_base_t level;

    /* disable interrupt */
    level = rt_hw_interrupt_disable();
    return level;
}

void sys_arch_unprotect(sys_prot_t pval)
{
    /* enable interrupt */
    rt_hw_interrupt_enable(pval);

    return;
}

void sys_arch_assert(const char *file, int line)
{
    rt_kprintf("\nAssertion: %d in %s, thread %s\n",
               line, file, rt_thread_self()->name);
    RT_ASSERT(0);
}

u32_t sys_jiffies(void)
{
    return rt_tick_get();
}

#endif /* !NO_SYS */

将 ethernetif.c 文件中有关信号量、线程等操作的部分,全部改成RT-Thread的实现方式

 

 

在  low_level_output() 函数中添加SCB_CleanInvalidateDCache();

如下图所示:

在 low_level_input() 函数中添加 SCB_CleanInvalidateDCache();

如下图所示:

 

 修改 lwip.c 文件

 

 六、修改 main.c 添加LWIP初始化函数

 若使用 AC6编译器,暂时不让 slipif.c 这个文件参与编译

 

 

 完。

注意重点

1.  在  low_level_output() 和 low_level_input() 函数中添加SCB_CleanInvalidateDCache();

2.  DP83848的PHY地址是在PHYCR(0x19)这个寄存器中,与LAN8742不一样,在LAN8742_Init()函数中改成读取PHYCR(0x19)寄存器。

当然我们也可以在程序手动赋值PHY地址,免去循环读取的步骤。

3.  由于CubeMX只能生成LAN8742的phy代码, 里面涉及到一些扩展寄存器,

一定要修改 LAN8742_GetLinkState(),这个函数是获取当前的以太网链路状态,由于DP83848和LAN8742的扩展寄存器不一样,所以必须修改成DP84848的寄存器(PHYSTS,0x10),不然当网线热插拔时LWIP就会卡死。

凡是涉及到扩展寄存器的,LAN8742和DP83848有所不同,可以自己修改相关的函数。

当网线热插拔时,可以在ethernet_link_status_updated()函数里添加一些自己想要的功能。

4.  LWIP的内存池和内存堆不能放在DTCM中,因为ETH DMA无法访问DTCM。

物联沃分享整理
物联沃-IOTWORD物联网 » 超详细 STM32H723 基于 DP83848 的网络通信系统设计:LWIP、RT-Thread(FreeRTOS)、STM32CubeMX 和 Keil MDK

发表评论