最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=95243
第8章 RL-TCPnet网络协议栈移植(FreeRTOS)
本章教程为大家讲解RL-TCPnet网络协议栈的FreeRTOS版本移植方式。
8.1 初学者重要提示
8.2 移植RL-TCPnet协议栈整体说明(必读)
8.3 第1步,安装指定的MDK软件包版本
8.4 第2步,准备一个工程模板
8.5 第3步,添加FreeRTOS源码
8.6 第4步,添加RT-TCPnet并配置
8.7 第5步,独立添加MAC和PHY驱动文件
8.8 第6步,初始化ETH_INIT.c文件
8.9 第7步,MPU和Cache配置文件bsp.c
8.10 第8步,更新bsp_timer.c和bsp.h文件
8.12 第10步,创建应用任务
8.13 常见移植错误总结
8.14 网络调试助手和板子的调试操作步骤
8.15 总结
8.1 初学者重要提示
- 学习本章节前,务必要优先学习第6章的底层驱动讲解。
- 测试时,请将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。而且使能了NetBIOS局域网域名,用户只需在电脑端ping armfly,就可以获得板子的IP地址。
- 如果要使用固定IP进行测试,请看附件章节A。
- 网口使用的是DM9161/9162(紧挨着9帧串口座的网口),而不是DM9000。
- 测试例子,务必看本章7.14小节的操作步骤。
8.2 移植RL-TCPnet协议栈整体说明(必读)
移植之前,有必要对移植过程有个整体的认识:
- 第1步,准备一个工程模板。
- 第2步,移植FreeRTOS。
- 移植FreeRTOS是采用MDK的RTE环境直接添加。当前H7芯片使用F强制运行STM32CubeMX,所以需要大家提前安装好STM32CubeMX V5.4或者以上版本。
- 特别注意几个网络任务的优先级安排。
AppTaskMsgPro任务 : osPriorityNormal2
AppTaskEthCheck任务 : osPriorityNormal3。
netCore_Thread任务 : osPriorityNormal3。
netEth0_Thread任务 : osPriorityNormal3。
osRtxTimerThread任务: osPriorityRealtime。
注意这个定时器任务osRtxTimerThread的优先级一定要最高,因为这个是RL-TCPnet的时间基准运行任务。
另外,对于FreeRTOS版本,任务AppTaskEthCheck,任务netCore_Thread和任务netEth0_Thread的优先级要相同,否则网络运行异常。
- 第3步,移植RL-TCPnet。
- 第4步,处理HAL库时间基准,以太网收发描述符内存区的MPU配置以及MAC,PHY驱动等相关问题。
- 第5步,创建应用。
总的来说,这5步就可以完成移植,这里还有一点比较重要的,需要放在开头说明,为了保证工程的独立性,教程中将RTE环境添加的HAL库文件,MAC驱动和PHY驱动独立了出来,并且单独制作了一个移植文件ETH_INIT.c,将GPIO设置,网线插拔消息,以太网中断等都汇总到这个文件里面,方便大家移植工程到自己的板子上。
下面将STM32H7的移植步骤和注意事项为大家做个说明。
8.3 第1步,安装指定的MDK软件包版本
移植新版RL-TCPnet网络协议栈需要大家下载指定的MDK软件包版本:
- CMSIS 软件包使用当前最新的:V5.6.0
- STM32H7使用当前最新的:V2.3.1
- CMSIS-Driver使用当前最新的:V10.2.0
- MDK中间件使用当前最新的:V7.12.0
- STM32CubeMX使用当前最新的:V5.4
- ARM_Compiler使用当前最新的:V1.6.1
- 这些软件包的安装在STM32H7用户手册的第2章2.3小节有详细说明。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 。
- 所有这些软件包汇总下载地址:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=95609 。
不管以后MDK的软件包版本如何升级,当前的软件包版本和以后的新版是可以同时安装的,也就是说可以安装多个不同版本,在这里可以选择指定版本:
8.4 第2步,准备一个工程模板
首先准备好一个简单的裸机工程模板,工程模板的制作就不做讲解了。这里要注意一点,由于我们要使用当前最新的HAL库软件包V1.6.0,需要大家使用这个帖子里面提供的例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=95244。
这里的重点是教大家移植RL-TCPnet协议栈。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):
8.5 第3步,添加FreeRTOS并配置
FreeRTOS可以方便的通过MDK的RTE环境添加进来。对于H7版本,MDK会强制运行一次CubeMX,并添加很多H7的HAL库文件,这些库文件我们可以使用,也可以不使用。教程配套的工程文件是不使用这些的,因为前面的工程模板里面已经添加了。所以要将这些文件全部隔离出来。
8.5.1 添加FreeRTOS源码
点击OK按钮后,弹出如下界面:
点击Start STM32CubeMX,这里需要大家电脑上已经安装了STM32CubeMX,并且为其安装了H7的软件包。
打开后,用户仅需配置如下地方即可:
然后点击右上角的GENERATE CODE:
然后弹出如下对话框,点击Close即可,然后关闭STM32CubeMX。
重新回到MDK,会有一个对话框,点击是即可:
最后就可以看到FreeRTOS源码已经添加到工程里面了:
8.5.2 将自动添加的库文件隔离出来
添加的所有文件中,仅FreeRTOS和文件stm32h7xx_hal_msp.c留下,其它文件全部隔离出来,隔离方法也比较简单,比如隔离生成的main.c函数,鼠标右击此文件选择Options for file ‘main.c’
然后取消掉include Target Build前的对勾,点击OK:
看到main.c文件上有个红色横杠,就表示已经隔离出来了:
同样的方法,将stm32h7xx_it.c文件也隔离出来,文件stm32h7xx_it.h不用管。
Device下面的这些文件也是同样的隔离方法:
只是鼠标右击弹出的界面有些不同:
注意stm32h7xx_hal_msp.c无需隔离,其它所有的文件全部隔离,stm32h7xx_hal_msp.c对于的隔离配置是STM32CubeMX,如果也隔离了,编译会有问题:
隔离后的效果如下:
8.5.3 FreeRTOS配置
剩下就是配置FreeRTOS,设置RTX_Config.h文件即可,移植阶段先按照如下设置配置好,后面章节会专门为大家讲解每个参数的配置含义:
8.6 第4步,添加RL-TCPnet并配置
像添加FreeRTOS一样,也可以通过RTE环境添加RL-TCPnet相关配置。
8.6.1 RL-TCPnet相关文件和驱动
CMSIS-Driver分组中添加MAC驱动和PHY驱动,其中PHY驱动随便选择一个即可,因为这个里面没有开发板上使用的DM9161/9162:
添加Event Recoder的支持,因为RL-TCPnet库的调试版本需要Event Recoder的支持:
添加ETH驱动:
添加网络配置:
我们需要的都已经添加好,效果如下:
红色方框里面的PHY和MAC驱动要隔离出来,通过下一步单独添加。
8.6.2 将自动添加的库文件隔离出来
文件stm32h7xx_hal_eth.c和stm32h7xx_hal_eth_ex.c也要隔离出来,我们单独在分组HAL_Driver里面添加:
8.6.3 RL-TCPnet配置
添加完毕RL-TCPnet所需的文件后,就是配置RL-TCPnet,具体每个配置所代表的含义,会在后面章节专为大家讲解。
Net_Config.c文件配置:
注意这个文件里面还有一个RL-TCPnet内核线程的优先级配置,当前是将其配置为:
osPriorityNormal:
Net_Config_ETH_0.h文件的配置如下:
这个文件里面有一个以太网接口任务的优先级配置,当前是将其配置为:osPriorityAboveNormal1。
其它文件Net_Config_TCP.h,Net_Config_UDP.h和Net_Debug.c使用默认配置即可。
8.7 第5步,独立添加MAC和PHY驱动文件
将我们裸机模板中制作好的RL-ARM文件夹复制粘贴到大家准备好的工程模板中。
RL-ARM文件夹中有如下七个文件夹,其中只有Driver文件夹里面有文件
然后将其也添加到工程文件里面:
文件PHY_DM916x和EMAC_STM32H7xx在第6章已经做了专门说明,这里重点把文件ETH_INIT.c文件做个说明。
8.8 第6步,初始化文件ETH_INIT.c
这个文件比较重要,将以太网收发缓冲,引脚配置,网线插拔状态和以太网中断设置都整理到了里面,方便大家将网络协议栈移植到自己的板子上。
这里将此文件为大家做个说明。
8.8.1 以太网描述符和收发缓冲区定义
以太网描述符和收发缓冲区定义如下:
/* Ethernet Rx DMA 描述符 */ __attribute__((at(0x30040000))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Tx DMA 描述符 */ __attribute__((at(0x30040060))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet 接收缓冲 */ __attribute__((at(0x30040200))) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_MAX_PACKET_SIZE];
以太网发送描述是定义在EMAC_STM32H7xx.c文件里面:
#ifndef EMAC_TXBUF_ADDRESS #define EMAC_TXBUF_ADDRESS 0x30042000 #endif static uint8_t TX_Buff[ETH_TX_DESC_CNT][ETH_MAX_PACKET_SIZE] __MEMORY_AT(EMAC_TXBUF_ADDRESS);
宏定义ETH_TX_DESC_CNT和ETH_RX_DESC_CNT在stm32h7xx_hal_conf.h文件里面:
#define ETH_TX_DESC_CNT 4 /* number of Ethernet Tx DMA descriptors */ #define ETH_RX_DESC_CNT 4 /* number of Ethernet Rx DMA descriptors */
宏定义ETH_MAX_PACKET_SIZE在文件stm32h7xx_hal_eth.h里面定义:
#define ETH_MAX_PACKET_SIZE ((uint32_t)1528U) /*!< ETH_HEADER + 2*VLAN_TAG + MAX_ETH_PAYLOAD + ETH_CRC */
有了这些认识后,还有一个关键点要认识到,H7的以太网收发描述符和收发缓存最好都定义到D2域的SRAM3空间,首地址是0x3004 0000,总大小32KB。
8.8.2 以太网消息通知函数
以太网消息通知函数如下:
/* 以太网连接状态,0和1都表示初始临时状态,2表示连接上,3表示断开 */ __IO uint8_t g_ucEthLinkStatus = 0; /* ********************************************************************************************************* * 函 数 名: netETH_Notify * 功能说明: 以太网状态消息 * 形 参: --- * 返 回 值: 无 ********************************************************************************************************* */ void netETH_Notify (uint32_t if_num, netETH_Event event, uint32_t val) { NET_ETH_LINK_INFO *info; switch (event) { case netETH_LinkDown: if(g_ucEthLinkStatus == 2) { g_ucEthLinkStatus = 3; } else { g_ucEthLinkStatus = 1; } printf_eth ("Link is down\r\n"); break; case netETH_LinkUp: g_ucEthLinkStatus = 2; printf_eth ("Link is up\r\n"); info = (NET_ETH_LINK_INFO *)&val; switch (info->speed) { case 0: printf_eth ("10 MBit\r\n"); break; case 1: printf_eth ("100 MBit\r\n"); break; case 2: printf_eth ("1 GBit\r\n"); break; } switch (info->duplex) { case 0: printf_eth ("Half duplex\r\n"); break; case 1: printf_eth ("Full duplex\r\n"); break; } break; case netETH_Wakeup: printf_eth ("Wakeup frame received\r\n"); break; case netETH_TimerAlarm: printf_eth ("Timer alarm\r\n"); break; } }
这里要注意变量g_ucEthLinkStatus = 1的情况。因为上电后,不管板子有没有插入网线,都会进入一次消息netETH_LinkDown,我们把这种情况用数值1来表示。
8.8.3 以太网引脚,时钟和中断配置
- 初始化部分:
/* ********************************************************************************************************* * 函 数 名: HAL_ETH_MspInit * 功能说明: 以太网初始化调用的底层回调,用于初始化IO,时钟和中断 * 形 参: --- * 返 回 值: 无 ********************************************************************************************************* */ void HAL_ETH_MspInit(ETH_HandleTypeDef* heth) { GPIO_InitTypeDef GPIO_InitStruct; /* PC1 ------> ETH_MDC PA1 ------> ETH_REF_CLK PA2 ------> ETH_MDIO PA7 ------> ETH_CRS_DV PC4 ------> ETH_RXD0 PC5 ------> ETH_RXD1 PB13 ------> ETH_TXD1 PG11 ------> ETH_TX_EN PG13 ------> ETH_TXD0 */ if(heth->Instance==ETH) { /* 使能外设时钟 */ __HAL_RCC_ETH1MAC_CLK_ENABLE(); __HAL_RCC_ETH1TX_CLK_ENABLE(); __HAL_RCC_ETH1RX_CLK_ENABLE(); /* 使能时钟 */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); /* 配置PA1, PA2 , PA7 */ GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL ; GPIO_InitStruct.Alternate = GPIO_AF11_ETH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* 配置PB13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* 配置PC1, PC4和PC5 */ GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /* 配置PG11, PG12和PG13 */ GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_13; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); /* 设置中断优先级 */ HAL_NVIC_SetPriority(ETH_IRQn, 3, 0); HAL_NVIC_EnableIRQ(ETH_IRQn); } }
这里要注意以太网中断优先级的配置,这里是将中断优先级配置为3。
- 复位初始化部分:
/* ********************************************************************************************************* * 函 数 名: HAL_ETH_MspDeInit * 功能说明: 以太网复位的回调函数,用于关闭时钟,GPIO和中断。 * 形 参: --- * 返 回 值: 无 ********************************************************************************************************* */ void HAL_ETH_MspDeInit(ETH_HandleTypeDef* heth) { if(heth->Instance==ETH) { /* 关闭时钟 */ __HAL_RCC_ETH1MAC_CLK_DISABLE(); __HAL_RCC_ETH1TX_CLK_DISABLE(); __HAL_RCC_ETH1RX_CLK_DISABLE(); HAL_GPIO_DeInit(GPIOC, GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5); HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7); HAL_GPIO_DeInit(GPIOB, GPIO_PIN_13); HAL_GPIO_DeInit(GPIOG, GPIO_PIN_11|GPIO_PIN_13); /* 关闭中断 */ HAL_NVIC_DisableIRQ(ETH_IRQn); } }
- 以太网中断服务程序
/* ********************************************************************************************************* * 函 数 名: ETH_IRQHandler * 功能说明: 以太网回调函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void ETH_IRQHandler(void) { HAL_ETH_IRQHandler(&heth); }
中断服务程序里面的这个全局变量heth是在ETH_INIT.c文件里面定义的。
8.9 第7步,MPU和Cache配置文件bsp.c
这个bsp.c文件也比较重要,移植阶段,直接将我们移植好的模板内容复制过去即可,这里把相关的内容为大家做个说明。
8.9.1 函数System_Init
系统初始化,主要是MPU,Cache和系统时钟配置,需要在FreeRTOS初始化之前调用。
/* ********************************************************************************************************* * 函 数 名: System_Init * 功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void System_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif }
8.9.2 函数bsp_Init
硬件外设的初始化,这个函数在FreeRTOS的启动任务里面调用。
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitTimer(); /* 初始化滴答定时器 */ }
8.9.3 函数SystemClock_Config
这个函数主要是完成系统时钟配置。
/* ********************************************************************************************************* * 函 数 名: SystemClock_Config * 功能说明: 初始化系统时钟 * System Clock source = PLL (HSE) * SYSCLK(Hz) = 400000000 (CPU Clock) * HCLK(Hz) = 200000000 (AXI and AHBs Clock) * AHB Prescaler = 2 * D1 APB3 Prescaler = 2 (APB3 Clock 100MHz) * D2 APB1 Prescaler = 2 (APB1 Clock 100MHz) * D2 APB2 Prescaler = 2 (APB2 Clock 100MHz) * D3 APB4 Prescaler = 2 (APB4 Clock 100MHz) * HSE Frequency(Hz) = 25000000 * PLL_M = 5 * PLL_N = 160 * PLL_P = 2 * PLL_Q = 4 * PLL_R = 2 * VDD(V) = 3.3 * Flash Latency(WS) = 4 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void SystemClock_Config(void) { 省略未写 /* AXI SRAM的时钟是上电自动使能的,而D2域的SRAM1,SRAM2和SRAM3要单独使能 */ #if 1 __HAL_RCC_D2SRAM1_CLK_ENABLE(); __HAL_RCC_D2SRAM2_CLK_ENABLE(); __HAL_RCC_D2SRAM3_CLK_ENABLE(); __HAL_RCC_BKPRAM_CLKAM_ENABLE(); __HAL_RCC_D3SRAM1_CLKAM_ENABLE(); #endif }
这里的RAM时钟初始化比较重要,这几个RAM的时钟都要单独使能。
8.9.4 函数MPU_Config
这个里面最重要的就是以太网收发描述符地址区的MPU配置:
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); 省略未写 /* 配置以太网收发描述符部分为Device */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x30040000; MPU_InitStruct.Size = MPU_REGION_SIZE_256B; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.SubRegionDisable = 0x0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }
务必要关闭以太网收发描述的Cache。
8.9.5 函数bsp_RunPer10ms
这个函数里面默认有个按键扫描,如果大家移植的程序里面没有按键初始化,务必要把这个按键扫描函数注释掉。
/* ********************************************************************************************************* * 函 数 名: bsp_RunPer10ms * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求 * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_RunPer10ms(void) { bsp_KeyScan10ms(); }
8.10 第8步,更新bsp_timer.c和bsp.h文件
更新bsp_timer.c文件是因为此文件跟FreeRTOS都要使用滴答定时器,有冲突。所以大家直接将我们工程模板里面此文件覆盖移植的这个文件即可。
bsp.h文件里面要添加一个宏定义,因为bsp_timer.c文件里面做了些条件编译:
#define USE_FreeRTOS 1 #if USE_FreeRTOS == 1 #include "FreeRTOS.h" #include "task.h" #define DISABLE_INT() taskENTER_CRITICAL() #define ENABLE_INT() taskEXIT_CRITICAL() #else /* 开关全局中断的宏 */ #define ENABLE_INT() __set_PRIMASK(0) /* 使能全局中断 */ #define DISABLE_INT() __set_PRIMASK(1) /* 禁止全局中断 */ #endif
8.11 第9步,HAL库时间基准stm32h7xx_hal_timbase_tim.c
由于FreeRTOS和HAL库需要一个时间基准,而且默认都是用的滴答定时器,所有要有一个选用其它的时间基准。当前的处理方案是为HAL库提供一个时间基准文件stm32h7xx_hal_timbase_tim.c。此文件里面做了两套方案,一个是使用TIM7做时间基准,另一个是使用FreeRTOS的API做时间基准,通过条件编译做选择。默认是采用FreeRTOS的API做时间基准。
/* ********************************************************************************************************* * 函 数 名: HAL_Delay * 功能说明: 重定向毫秒延迟函数。替换HAL中的函数。因为HAL中的缺省函数依赖于Systick中断,如果在USB、SD * 卡中断中有延迟函数,则会锁死。也可以通过函数HAL_NVIC_SetPriority提升Systick中断 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void HAL_Delay(uint32_t Delay) { bsp_DelayMS(Delay); } HAL_StatusTypeDef HAL_InitTick (uint32_t TickPriority) { return HAL_OK; } uint32_t HAL_GetTick (void) { static uint32_t ticks = 0U; uint32_t i; if (osKernelGetState () == osKernelRunning) { return ((uint32_t)osKernelGetTickCount ()); } /* 如果FreeRTOS还没有运行,采用下面方式 */ for (i = (SystemCoreClock >> 14U); i > 0U; i--) { __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } return ++ticks; }
8.12 第10步,创建应用任务
FreeRTOS和RL-TCPnet都移植完毕后,就可以添加应用任务验证是否移植成功了。
有三个文件需要设计,移植阶段,直接添加到自己移植的工程里面即可。
- 文件main.c
主要功能是FreeRTOS任务的创建和执行。
- 文件includes.h
主要功能是各种头文件的汇总。
- 文件app_tcpnet_lib.c
主要功能是TCPnet的应用任务设计。
8.13 常见移植错误总结
常见的移植错误主要有下面几种情况:
- 编译没有错误,下载进去后ping不通过。
解决办法:重新上电即可。
- 编译后,提示错误cannot open source input file "Driver_ETH_PHY.h"
出现这个错误的解决办法是将新版CMSIS软件包里面Driver文件夹全部复制到工程里面的CMSIS文件夹下,并添加路径:
- 编译后提示如下两种错误:
Error: L6200E: Symbol PendSV_Handler multiply defined (by irq_cm4f.o and stm32h7xx_it.o).
Error: L6200E: Symbol SVC_Handler multiply defined (by irq_cm4f.o and stm32h7xx_it.o).
解决办法:这是函数重定义了,直接将stm32h7xx_it.c文件里面的PendSV_Handler和SVC_Handler删掉。
- 提示如下错误
Error: L6218E: Undefined symbol bsp_DelayMS (referred from bsp_fmc_io.o).
解决办法:打开bsp_dwt.C文件中的条件编译。
8.14 网络调试助手和板子的调试操作步骤
我们这里使用下面这款调试助手,当然,任何其它网络调试助手均可,不限制:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=1568 。
8.14.1 测试使用的DM916X网口并注意跳线帽
测试时,网线要插到DM916X网口上:
特别注意此处跳线帽的位置,要短接PG11:
最后,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获取板子的IP地址。
如果使用固定IP方式,看附件章节A。
8.14.2 RJ45网络变压器插座上绿灯和黄灯现象
各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。
绿灯:长亮代表100M; 不亮代表10M。
黄灯:长亮代表无数据收发; 闪烁代表有数据收发。
也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。
对于开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。
8.14.3 网线插拔的各种情况
此贴对各种网线插拔情况进行了总结,并且当前配套模板程序也做了支持:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=95386 。
8.14.4 第1步,获取板子IP地址
首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获取板子的IP地址。
ping命令的主要作用是通过发送数据包并接收应答信息来检测两台设备之间的网络是否连通。ping命令成功说明当前主机与目的主机之间存在连通的路径。如果不成功,需要查看网线是否连通、网卡设置是否正确、IP地址是否可用等。测试方法如下:
- WIN+R组合键打开“运行”窗口,输入cmd。
- 弹出的命令窗口中,输入ping armfly。
- 输入ping armfly后,回车。
收发相同,没有数据丢失,说明ping命令也是成功的。获得IP地址是192.168.1.5。
8.14.5 第2步,网络调试助手创建TCP客户端
- 打开调试助手,点击左上角创建连接:
- 弹出如下界面,类型选择TCP,目标IP设置为192.168.1.5,端口号1001,最后点击创建:
特别说明,我们这里直接填局域网域名armfly也是没有问题的,即下面这样:
- 创建后的界面效果如下:
- 点击连接,连接后的界面效果如下:
连接上后,串口软件也会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):
8.14.6 第3步,TCP服务器发送数据
板子和网络调试助手建立连接后就可以相互收发数据了。对于发送数据。程序中创建了三种大小的数据发送测试。
- K1按键按下,发送了8个字符,从1到8。
- K2按键按下,发送1024字节,每次发送数据包的前8个字节设置了字符a到字符h,后面未做设置。
- K3按键按下,发送5*1024*1024 = 5242880字节,即5MB。每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
8.14.7 第4步,TCP服务器接收数据
TCP服务器接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送0到9,共10个字符:
点击发送后,可以看到串口软件打印出接收到的10个字符:
测试也是没问题的。
8.15 总结
本章节为大家讲解了RL-TCPnet网络协议栈的FreeRTOS版本移植方法,移植涉及到的知识点比较多,初学的话,建议实际动手操作一遍。
第8章 RL-TCPnet网络协议栈移植(FreeRTOS)
本章教程为大家讲解RL-TCPnet网络协议栈的FreeRTOS版本移植方式。
8.1 初学者重要提示
8.2 移植RL-TCPnet协议栈整体说明(必读)
8.3 第1步,安装指定的MDK软件包版本
8.4 第2步,准备一个工程模板
8.5 第3步,添加FreeRTOS源码
8.6 第4步,添加RT-TCPnet并配置
8.7 第5步,独立添加MAC和PHY驱动文件
8.8 第6步,初始化ETH_INIT.c文件
8.9 第7步,MPU和Cache配置文件bsp.c
8.10 第8步,更新bsp_timer.c和bsp.h文件
8.12 第10步,创建应用任务
8.13 常见移植错误总结
8.14 网络调试助手和板子的调试操作步骤
8.15 总结
8.1 初学者重要提示
u 学习本章节前,务必要优先学习第6章的底层驱动讲解。
u 测试时,请将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
而且使能了NetBIOS局域网域名,用户只需在电脑端ping armfly,就可以获得板子的IP地址。
u 如果要使用固定IP进行测试,请看附件章节A。
u 网口使用的是DM9161/9162(紧挨着9帧串口座的网口),而不是DM9000。
u 测试例子,务必看本章7.14小节的操作步骤。
8.2 移植RL-TCPnet协议栈整体说明(必读)
移植之前,有必要对移植过程有个整体的认识:
u 第1步,准备一个工程模板。
u 第2步,移植FreeRTOS。
l 移植FreeRTOS是采用MDK的RTE环境直接添加。当前H7芯片使用F强制运行STM32CubeMX,所以需要大家提前安装好STM32CubeMX V5.4或者以上版本。
l 特别注意几个网络任务的优先级安排。
AppTaskMsgPro任务 :osPriorityNormal2
AppTaskEthCheck任务 : osPriorityNormal3。
netCore_Thread任务 :osPriorityNormal3。
netEth0_Thread任务 : osPriorityNormal3。
osRtxTimerThread任务:osPriorityRealtime。
注意这个定时器任务osRtxTimerThread的优先级一定要最高,因为这个是RL-TCPnet的时间基准运行任务。
另外,对于FreeRTOS版本,任务AppTaskEthCheck,任务netCore_Thread和任务netEth0_Thread的优先级要相同,否则网络运行异常。
u 第3步,移植RL-TCPnet。
u 第4步,处理HAL库时间基准,以太网收发描述符内存区的MPU配置以及MAC,PHY驱动等相关问题。
u 第5步,创建应用。
总的来说,这5步就可以完成移植,这里还有一点比较重要的,需要放在开头说明,为了保证工程的独立性,教程中将RTE环境添加的HAL库文件,MAC驱动和PHY驱动独立了出来,并且单独制作了一个移植文件ETH_INIT.c,将GPIO设置,网线插拔消息,以太网中断等都汇总到这个文件里面,方便大家移植工程到自己的板子上。
下面将STM32H7的移植步骤和注意事项为大家做个说明。
8.3 第1步,安装指定的MDK软件包版本
移植新版RL-TCPnet网络协议栈需要大家下载指定的MDK软件包版本:
u CMSIS 软件包使用当前最新的:V5.6.0
u STM32H7使用当前最新的:V2.3.1
u CMSIS-Driver使用当前最新的:V10.2.0
u MDK中间件使用当前最新的:V7.12.0
u STM32CubeMX使用当前最新的:V5.4
u ARM_Compiler使用当前最新的:V1.6.1
u 这些软件包的安装在STM32H7用户手册的第2章2.3小节有详细说明。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980。
u 所有这些软件包汇总下载地址:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=95609。
不管以后MDK的软件包版本如何升级,当前的软件包版本和以后的新版是可以同时安装的,也就是说可以安装多个不同版本,在这里可以选择指定版本:
8.4 第2步,准备一个工程模板
首先准备好一个简单的裸机工程模板,工程模板的制作就不做讲解了。这里要注意一点,由于我们要使用当前最新的HAL库软件包V1.6.0,需要大家使用这个帖子里面提供的例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=95244。
这里的重点是教大家移植RL-TCPnet协议栈。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):
8.5 第3步,添加FreeRTOS并配置
FreeRTOS可以方便的通过MDK的RTE环境添加进来。对于H7版本,MDK会强制运行一次CubeMX,并添加很多H7的HAL库文件,这些库文件我们可以使用,也可以不使用。教程配套的工程文件是不使用这些的,因为前面的工程模板里面已经添加了。所以要将这些文件全部隔离出来。
8.5.1 添加FreeRTOS源码
点击OK按钮后,弹出如下界面:
点击Start STM32CubeMX,这里需要大家电脑上已经安装了STM32CubeMX,并且为其安装了H7的软件包。
打开后,用户仅需配置如下地方即可:
然后点击右上角的GENERATE CODE:
然后弹出如下对话框,点击Close即可,然后关闭STM32CubeMX。
重新回到MDK,会有一个对话框,点击是即可:
最后就可以看到FreeRTOS源码已经添加到工程里面了:
8.5.2 将自动添加的库文件隔离出来
添加的所有文件中,仅FreeRTOS和文件stm32h7xx_hal_msp.c留下,其它文件全部隔离出来,隔离方法也比较简单,比如隔离生成的main.c函数,鼠标右击此文件选择Options for file ‘main.c’
然后取消掉include Target Build前的对勾,点击OK:
看到main.c文件上有个红色横杠,就表示已经隔离出来了:
同样的方法,将stm32h7xx_it.c文件也隔离出来,文件stm32h7xx_it.h不用管。
Device下面的这些文件也是同样的隔离方法:
只是鼠标右击弹出的界面有些不同:
注意stm32h7xx_hal_msp.c无需隔离,其它所有的文件全部隔离,stm32h7xx_hal_msp.c对于的隔离配置是STM32CubeMX,如果也隔离了,编译会有问题:
隔离后的效果如下:
8.5.3 FreeRTOS配置
剩下就是配置FreeRTOS,设置RTX_Config.h文件即可,移植阶段先按照如下设置配置好,后面章节会专门为大家讲解每个参数的配置含义:
8.6 第4步,添加RL-TCPnet并配置
像添加FreeRTOS一样,也可以通过RTE环境添加RL-TCPnet相关配置。
8.6.1 RL-TCPnet相关文件和驱动
CMSIS-Driver分组中添加MAC驱动和PHY驱动,其中PHY驱动随便选择一个即可,因为这个里面没有开发板上使用的DM9161/9162:
添加Event Recoder的支持,因为RL-TCPnet库的调试版本需要Event Recoder的支持:
添加ETH驱动:
添加网络配置:
我们需要的都已经添加好,效果如下:
红色方框里面的PHY和MAC驱动要隔离出来,通过下一步单独添加。
8.6.2 将自动添加的库文件隔离出来
文件stm32h7xx_hal_eth.c和stm32h7xx_hal_eth_ex.c也要隔离出来,我们单独在分组HAL_Driver里面添加:
8.6.3 RL-TCPnet配置
添加完毕RL-TCPnet所需的文件后,就是配置RL-TCPnet,具体每个配置所代表的含义,会在后面章节专为大家讲解。
Net_Config.c文件配置:
注意这个文件里面还有一个RL-TCPnet内核线程的优先级配置,当前是将其配置为:
osPriorityNormal:
Net_Config_ETH_0.h文件的配置如下:
这个文件里面有一个以太网接口任务的优先级配置,当前是将其配置为:osPriorityAboveNormal1。
其它文件Net_Config_TCP.h,Net_Config_UDP.h和Net_Debug.c使用默认配置即可。
8.7 第5步,独立添加MAC和PHY驱动文件
将我们裸机模板中制作好的RL-ARM文件夹复制粘贴到大家准备好的工程模板中。
RL-ARM文件夹中有如下七个文件夹,其中只有Driver文件夹里面有文件
然后将其也添加到工程文件里面:
文件PHY_DM916x和EMAC_STM32H7xx在第6章已经做了专门说明,这里重点把文件ETH_INIT.c文件做个说明。
8.8 第6步,初始化文件ETH_INIT.c
这个文件比较重要,将以太网收发缓冲,引脚配置,网线插拔状态和以太网中断设置都整理到了里面,方便大家将网络协议栈移植到自己的板子上。
这里将此文件为大家做个说明。
8.8.1 以太网描述符和收发缓冲区定义
以太网描述符和收发缓冲区定义如下:
/* Ethernet Rx DMA 描述符 */
__attribute__((at(0x30040000))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT];
/* Ethernet Tx DMA 描述符 */
__attribute__((at(0x30040060))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT];
/* Ethernet 接收缓冲 */
__attribute__((at(0x30040200))) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_MAX_PACKET_SIZE];
以太网发送描述是定义在EMAC_STM32H7xx.c文件里面:
#ifndef EMAC_TXBUF_ADDRESS
#define EMAC_TXBUF_ADDRESS 0x30042000
#endif
static uint8_t TX_Buff[ETH_TX_DESC_CNT][ETH_MAX_PACKET_SIZE] __MEMORY_AT(EMAC_TXBUF_ADDRESS);
宏定义ETH_TX_DESC_CNT和ETH_RX_DESC_CNT在stm32h7xx_hal_conf.h文件里面:
#define ETH_TX_DESC_CNT 4 /* number of Ethernet Tx DMA descriptors */
#define ETH_RX_DESC_CNT 4 /* number of Ethernet Rx DMA descriptors */
宏定义ETH_MAX_PACKET_SIZE在文件stm32h7xx_hal_eth.h里面定义:
#define ETH_MAX_PACKET_SIZE ((uint32_t)1528U) /*!< ETH_HEADER + 2*VLAN_TAG + MAX_ETH_PAYLOAD + ETH_CRC */
有了这些认识后,还有一个关键点要认识到,H7的以太网收发描述符和收发缓存最好都定义到D2域的SRAM3空间,首地址是0x3004 0000,总大小32KB。
8.8.2 以太网消息通知函数
以太网消息通知函数如下:
/* 以太网连接状态,0和1都表示初始临时状态,2表示连接上,3表示断开 */
__IO uint8_t g_ucEthLinkStatus = 0;
/*
*********************************************************************************************************
* 函 数 名: netETH_Notify
* 功能说明: 以太网状态消息
* 形 参: ---
* 返 回 值: 无
*********************************************************************************************************
*/
void netETH_Notify (uint32_t if_num, netETH_Event event, uint32_t val)
{
NET_ETH_LINK_INFO *info;
switch (event)
{
case netETH_LinkDown:
if(g_ucEthLinkStatus == 2)
{
g_ucEthLinkStatus = 3;
}
else
{
g_ucEthLinkStatus = 1;
}
printf_eth ("Link is down\r\n");
break;
case netETH_LinkUp:
g_ucEthLinkStatus = 2;
printf_eth ("Link is up\r\n");
info = (NET_ETH_LINK_INFO *)&val;
switch (info->speed)
{
case 0:
printf_eth ("10 MBit\r\n");
break;
case 1:
printf_eth ("100 MBit\r\n");
break;
case 2:
printf_eth ("1 GBit\r\n");
break;
}
switch (info->duplex)
{
case 0:
printf_eth ("Half duplex\r\n");
break;
case 1:
printf_eth ("Full duplex\r\n");
break;
}
break;
case netETH_Wakeup:
printf_eth ("Wakeup frame received\r\n");
break;
case netETH_TimerAlarm:
printf_eth ("Timer alarm\r\n");
break;
}
}
这里要注意变量g_ucEthLinkStatus = 1的情况。因为上电后,不管板子有没有插入网线,都会进入一次消息netETH_LinkDown,我们把这种情况用数值1来表示。
8.8.3 以太网引脚,时钟和中断配置
初始化部分:
/*
*********************************************************************************************************
* 函 数 名: HAL_ETH_MspInit
* 功能说明: 以太网初始化调用的底层回调,用于初始化IO,时钟和中断
* 形 参: ---
* 返 回 值: 无
*********************************************************************************************************
*/
void HAL_ETH_MspInit(ETH_HandleTypeDef* heth)
{
GPIO_InitTypeDef GPIO_InitStruct;
/*
PC1 ------> ETH_MDC
PA1 ------> ETH_REF_CLK
PA2 ------> ETH_MDIO
PA7 ------> ETH_CRS_DV
PC4 ------> ETH_RXD0
PC5 ------> ETH_RXD1
PB13 ------> ETH_TXD1
PG11 ------> ETH_TX_EN
PG13 ------> ETH_TXD0
*/
if(heth->Instance==ETH)
{
/* 使能外设时钟 */
__HAL_RCC_ETH1MAC_CLK_ENABLE();
__HAL_RCC_ETH1TX_CLK_ENABLE();
__HAL_RCC_ETH1RX_CLK_ENABLE();
/* 使能时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/* 配置PA1, PA2 , PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL ;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置PB13 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 配置PC1, PC4和PC5 */
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* 配置PG11, PG12和PG13 */
GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_13;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/* 设置中断优先级 */
HAL_NVIC_SetPriority(ETH_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
}
这里要注意以太网中断优先级的配置,这里是将中断优先级配置为3。
复位初始化部分:
/*
*********************************************************************************************************
* 函 数 名: HAL_ETH_MspDeInit
* 功能说明: 以太网复位的回调函数,用于关闭时钟,GPIO和中断。
* 形 参: ---
* 返 回 值: 无
*********************************************************************************************************
*/
void HAL_ETH_MspDeInit(ETH_HandleTypeDef* heth)
{
if(heth->Instance==ETH)
{
/* 关闭时钟 */
__HAL_RCC_ETH1MAC_CLK_DISABLE();
__HAL_RCC_ETH1TX_CLK_DISABLE();
__HAL_RCC_ETH1RX_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5);
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7);
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_13);
HAL_GPIO_DeInit(GPIOG, GPIO_PIN_11|GPIO_PIN_13);
/* 关闭中断 */
HAL_NVIC_DisableIRQ(ETH_IRQn);
}
}
u 以太网中断服务程序
/*
*********************************************************************************************************
* 函 数 名: ETH_IRQHandler
* 功能说明: 以太网回调函数
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void ETH_IRQHandler(void)
{
HAL_ETH_IRQHandler(&heth);
}
中断服务程序里面的这个全局变量heth是在ETH_INIT.c文件里面定义的。
8.9 第7步,MPU和Cache配置文件bsp.c
这个bsp.c文件也比较重要,移植阶段,直接将我们移植好的模板内容复制过去即可,这里把相关的内容为大家做个说明。
8.9.1 函数System_Init
系统初始化,主要是MPU,Cache和系统时钟配置,需要在FreeRTOS初始化之前调用。
/*
*********************************************************************************************************
* 函 数 名: System_Init
* 功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void System_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init();
/*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
}
8.9.2 函数bsp_Init
硬件外设的初始化,这个函数在FreeRTOS的启动任务里面调用。
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */
bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
bsp_InitTimer(); /* 初始化滴答定时器 */
}
8.9.3 函数SystemClock_Config
这个函数主要是完成系统时钟配置。
/*
*********************************************************************************************************
* 函 数 名: SystemClock_Config
* 功能说明: 初始化系统时钟
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 400000000 (CPU Clock)
* HCLK(Hz) = 200000000 (AXI and AHBs Clock)
* AHB Prescaler = 2
* D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
* D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
* D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
* D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
* HSE Frequency(Hz) = 25000000
* PLL_M = 5
* PLL_N = 160
* PLL_P = 2
* PLL_Q = 4
* PLL_R = 2
* VDD(V) = 3.3
* Flash Latency(WS) = 4
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void SystemClock_Config(void)
{
省略未写
/* AXI SRAM的时钟是上电自动使能的,而D2域的SRAM1,SRAM2和SRAM3要单独使能 */
#if 1
__HAL_RCC_D2SRAM1_CLK_ENABLE();
__HAL_RCC_D2SRAM2_CLK_ENABLE();
__HAL_RCC_D2SRAM3_CLK_ENABLE();
__HAL_RCC_BKPRAM_CLKAM_ENABLE();
__HAL_RCC_D3SRAM1_CLKAM_ENABLE();
#endif
}
这里的RAM时钟初始化比较重要,这几个RAM的时钟都要单独使能。
8.9.4 函数MPU_Config
这个里面最重要的就是以太网收发描述符地址区的MPU配置:
/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
省略未写
/* 配置以太网收发描述符部分为Device */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
务必要关闭以太网收发描述的Cache。
8.9.5 函数bsp_RunPer10ms
这个函数里面默认有个按键扫描,如果大家移植的程序里面没有按键初始化,务必要把这个按键扫描函数注释掉。
/*
*********************************************************************************************************
* 函 数 名: bsp_RunPer10ms
* 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
* 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
bsp_KeyScan10ms();
}
8.10第8步,更新bsp_timer.c和bsp.h文件
更新bsp_timer.c文件是因为此文件跟FreeRTOS都要使用滴答定时器,有冲突。所以大家直接将我们工程模板里面此文件覆盖移植的这个文件即可。
bsp.h文件里面要添加一个宏定义,因为bsp_timer.c文件里面做了些条件编译:
#define USE_FreeRTOS 1
#if USE_FreeRTOS == 1
#include "FreeRTOS.h"
#include "task.h"
#define DISABLE_INT() taskENTER_CRITICAL()
#define ENABLE_INT() taskEXIT_CRITICAL()
#else
/* 开关全局中断的宏 */
#define ENABLE_INT() __set_PRIMASK(0) /* 使能全局中断 */
#define DISABLE_INT() __set_PRIMASK(1) /* 禁止全局中断 */
#endif
8.11第9步,HAL库时间基准stm32h7xx_hal_timbase_tim.c
由于FreeRTOS和HAL库需要一个时间基准,而且默认都是用的滴答定时器,所有要有一个选用其它的时间基准。当前的处理方案是为HAL库提供一个时间基准文件stm32h7xx_hal_timbase_tim.c。此文件里面做了两套方案,一个是使用TIM7做时间基准,另一个是使用FreeRTOS的API做时间基准,通过条件编译做选择。默认是采用FreeRTOS的API做时间基准。
/*
*********************************************************************************************************
* 函 数 名: HAL_Delay
* 功能说明: 重定向毫秒延迟函数。替换HAL中的函数。因为HAL中的缺省函数依赖于Systick中断,如果在USB、SD
* 卡中断中有延迟函数,则会锁死。也可以通过函数HAL_NVIC_SetPriority提升Systick中断
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void HAL_Delay(uint32_t Delay)
{
bsp_DelayMS(Delay);
}
HAL_StatusTypeDef HAL_InitTick (uint32_t TickPriority)
{
return HAL_OK;
}
uint32_t HAL_GetTick (void)
{
static uint32_t ticks = 0U;
uint32_t i;
if (osKernelGetState () == osKernelRunning)
{
return ((uint32_t)osKernelGetTickCount ());
}
/* 如果FreeRTOS还没有运行,采用下面方式 */
for (i = (SystemCoreClock >> 14U); i > 0U; i--)
{
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
}
return ++ticks;
}
8.12第10步,创建应用任务
FreeRTOS和RL-TCPnet都移植完毕后,就可以添加应用任务验证是否移植成功了。
有三个文件需要设计,移植阶段,直接添加到自己移植的工程里面即可。
u 文件main.c
主要功能是FreeRTOS任务的创建和执行。
u 文件includes.h
主要功能是各种头文件的汇总。
u 文件app_tcpnet_lib.c
主要功能是TCPnet的应用任务设计。
8.13常见移植错误总结
常见的移植错误主要有下面几种情况:
u 编译没有错误,下载进去后ping不通过。
解决办法:重新上电即可。
u 编译后,提示错误cannot open source input file "Driver_ETH_PHY.h"
出现这个错误的解决办法是将新版CMSIS软件包里面Driver文件夹全部复制到工程里面的CMSIS文件夹下,并添加路径:
u 编译后提示如下两种错误:
Error: L6200E: Symbol PendSV_Handler multiply defined (by irq_cm4f.o and stm32h7xx_it.o).
Error: L6200E: Symbol SVC_Handler multiply defined (by irq_cm4f.o and stm32h7xx_it.o).
解决办法:这是函数重定义了,直接将stm32h7xx_it.c文件里面的PendSV_Handler和SVC_Handler删掉。
u 提示如下错误
Error: L6218E: Undefined symbol bsp_DelayMS (referred from bsp_fmc_io.o).
解决办法:打开bsp_dwt.C文件中的条件编译。
8.14网络调试助手和板子的调试操作步骤
我们这里使用下面这款调试助手,当然,任何其它网络调试助手均可,不限制:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=1568。
8.14.1测试使用的DM916X网口并注意跳线帽
测试时,网线要插到DM916X网口上:
特别注意此处跳线帽的位置,要短接PG11:
最后,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获取板子的IP地址。
如果使用固定IP方式,看附件章节A。
8.14.2RJ45网络变压器插座上绿灯和黄灯现象
各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。
绿灯:长亮代表100M; 不亮代表10M。
黄灯:长亮代表无数据收发; 闪烁代表有数据收发。
也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。
对于开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。
8.14.3网线插拔的各种情况
此贴对各种网线插拔情况进行了总结,并且当前配套模板程序也做了支持:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=95386。
8.14.4第1步,获取板子IP地址
首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获取板子的IP地址。
ping命令的主要作用是通过发送数据包并接收应答信息来检测两台设备之间的网络是否连通。ping命令成功说明当前主机与目的主机之间存在连通的路径。如果不成功,需要查看网线是否连通、网卡设置是否正确、IP地址是否可用等。测试方法如下:
l WIN+R组合键打开“运行”窗口,输入cmd。
l 弹出的命令窗口中,输入ping armfly。
l 输入ping armfly后,回车。
收发相同,没有数据丢失,说明ping命令也是成功的。获得IP地址是192.168.1.5。
8.14.5第2步,网络调试助手创建TCP客户端
u 打开调试助手,点击左上角创建连接:
u 弹出如下界面,类型选择TCP,目标IP设置为192.168.1.5,端口号1001,最后点击创建:
特别说明,我们这里直接填局域网域名armfly也是没有问题的,即下面这样:
u 创建后的界面效果如下:
u 点击连接,连接后的界面效果如下:
连接上后,串口软件也会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):
8.14.6第3步,TCP服务器发送数据
板子和网络调试助手建立连接后就可以相互收发数据了。对于发送数据。程序中创建了三种大小的数据发送测试。
u K1按键按下,发送了8个字符,从1到8。
u K2按键按下,发送1024字节,每次发送数据包的前8个字节设置了字符a到字符h,后面未做设置。
u K3按键按下,发送5*1024*1024 = 5242880字节,即5MB。每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
8.14.7第4步,TCP服务器接收数据
TCP服务器接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送0到9,共10个字符:
点击发送后,可以看到串口软件打印出接收到的10个字符:
测试也是没问题的。
8.15总结
本章节为大家讲解了RL-TCPnet网络协议栈的FreeRTOS版本移植方法,移植涉及到的知识点比较多,初学的话,建议实际动手操作一遍。