话不多说,开搞。
首先是CubeMX配置,我们打开软件后,选择自己所需要的单片机型号(我的STM32H755ZIT6),我用的是官方开发板(NUCLEO-H755ZI-Q),然后就可以开始我们的配置了,如下图所示,针对H7系列多了一个L1 cache ,故我们需要先配置下MPU:
详解:1,使能MPU,使能ICache DCache;
2,设置地址为0x30000000,即D2域的SRAM1的起始地址;
3,访问权限,全部允许 ALL ACCESS PERMITTED
4,MPU性能配置为 :
TEX:0;
memory type:device;
cacheable :disable;
bufferable:enable;
sharable:enable;
这样可以充分发挥缓存的性能,这里我也不是特别明白,大家也可以根据自己的需要配置,如下图:
然后我们需要配置下时钟晶振,由于我的开发板上没有外部晶振,所以我选择了内部高速时钟HSI:
详解:1,针对我的H755开发板,这里必须选择SMPS直接供电(否则会导致单片机自锁),大家可以根据自己的硬件电路设计选择LDO供电,不过还是推荐大家设计成内部SMPS供电方式,可以大大提高性能;
2,供电范围选择 scale1,这样可以配置更高的时钟频率,我这里配置为400MHz,如下图:
然后就可以配置我们的以太网了,如下图:
详解:RX描述符地址我们配置为0x300000000,TX描述符地址我们配置为0x30000080,描述符长度为4,RX缓冲区长度为1524;
这里需要特别注意的是:对于CubeMX6.10.0版本,这里没有了RX接收缓冲区地址的配置,而且在代码中也未找到对该缓冲区地址的定义,所以可能是系统自动分配的,如果我们采用默认的RAM地址0x20000000,可能会把RX接收缓冲区的地址分配到0x20000000位置下,然而这个空间属于DI域,ETH是无法访问的,所以在生成代码后我们需要修改一下RAM的地址,如下图(选中RAM2改为0x24000000,SIZE改为0x80000):
然后选择linker选项卡,勾选use memory layout from target dialog,大家也可以尝试下修改sct文件,配置下RAM的地址:
上面两张图片只针对M7核,cortexM4可以不做修改。
由于我们时钟频率配置为400MHz,所以对于ETH的通信管脚,我们都配置为very high,否则可能会导致ping不通。
接下来我们配置LWIP,如下图:
详解:
这里我们选择了静态IP的方式,需要保证单片机和自己的电脑配置在一个网段内,其他保持默认即可;
然后是key option 选项卡,如下图:
详解:
这里MEM_SIZE我们配置为10k大小
LWIP_RAM_HEAP_POINTER地址配置为0x30002000
这里也可以根据自己的需要来配置SIZE大小和RAM_HEAP的地址
接下是我们使能LWIP_NETIF_STATUS_CALLBACK回调函数:
然后配置下PHY芯片:
配置一个LED灯方便我们调试:
打开project manager 选项卡,把M7和的堆栈大小改一下:
现在点击generate code 来生成代码,生成代码后我们还需要做一些必要的修改:(只针对AC6编译器)
首先点击魔术棒,选择编译器为AC6,修改RAM地址,上文已讲过;
然后打开cc.h文件,修改下列两行代码(大概在45,46行),屏蔽掉do后面的语句(大概82行,这样就不需要勾选微库了)
#define LWIP_TIMEVAL_PRIVATE 1
//#include <sys/time.h>
#define LWIP_PLATFORM_ASSERT(x) //do {printf("Assertion \"%s\" failed at line %d in %s\n", \
x, __LINE__, __FILE__); } while(0)
打开ethernetif.c文件,修改编译部分的代码:
#if defined ( __ICCARM__ ) /*!< IAR Compiler */
#pragma location=0x30000000
ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
#pragma location=0x30000080
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
#elif defined ( __CC_ARM ) /* MDK ARM Compiler */
__attribute__((at(0x30000000))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
__attribute__((at(0x30000080))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
#elif defined ( __GNUC__ ) /* GNU Compiler */
//ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDecripSection"))); /* Ethernet Rx DMA Descriptors */
//ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDecripSection"))); /* Ethernet Tx DMA Descriptors */
#if !((__ARMCC_VERSION >= 6010050))
ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDecripSection"))); /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDecripSection"))); /* Ethernet Tx DMA Descriptors */
#else
ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".bss.ARM.__at_0x30000000"))); /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".bss.ARM.__at_0x30000080"))); /* Ethernet Tx DMA Descriptors */
#endif
#endif
在low_level_output函数中发送之前添加SCB_CleanInvalidateDCache();函数:
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
uint32_t i = 0U;
struct pbuf *q = NULL;
err_t errval = ERR_OK;
ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};
memset(Txbuffer, 0 , ETH_TX_DESC_CNT*sizeof(ETH_BufferTypeDef));
for(q = p; q != NULL; q = q->next)
{
if(i >= ETH_TX_DESC_CNT)
return ERR_IF;
Txbuffer[i].buffer = q->payload;
Txbuffer[i].len = q->len;
if(i>0)
{
Txbuffer[i-1].next = &Txbuffer[i];
}
if(q->next == NULL)
{
Txbuffer[i].next = NULL;
}
i++;
}
TxConfig.Length = p->tot_len;
TxConfig.TxBuffer = Txbuffer;
TxConfig.pData = p;
SCB_CleanInvalidateDCache();
HAL_ETH_Transmit(&heth, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);
return errval;
}
由于我的开发板PHY芯片的复位信号和单片机的复位信号是连接在一起的,所以在main函数中LWIP初始化之前我们加一段100ms的延时,等PHY复位稳定后,再进行PHY芯片的初始化操作,在while循环中添加下列测试代码:
while (1)
{
MX_LWIP_Process();
HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin);
HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
然后编译下载程序后运行,我们打开cmd命令窗口ping下:
最后,说一下我采用的驱动库版本是STM32Cube FW_H7 V1.11.2,LWIP版本是 V2.1.2,不同的版本库文件可能会有区别,请大家仔细甄别。