上一版本移植并没有写的很详细,只是将改好的代码贴上去,今天更新一版,附带资源。上一版本用的是FreeRTOS V10.0.1.这一版采用了最新的FreeRTOS V10.3.1
在正确移植FreeRTOS的工程中Libraries文件夹下创建FreeRTOS-TCP文件夹用来存放TCP源码,将FreeRTOS-Plus文件源码中FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP路径下的全部文件拷贝到新建的FreeRTOS-TCP文件夹中。
打开工程,添加分组FreeRTOS_TCP/Source,用来存放TCP源码。将FreeRTOS_TCP路径下的C文件添加到FreeRTOS_TCP分组中。将FreeRTOS-TCP\portable\BufferManagement路径下的内存管理文件BufferAllocation_c、FreeRTOS-TCP\portable\NetworkInterface\STM32Fxx路径下的接口文件NetworkInterface.c和FreeRTOS-TCP\portable\NetworkInterface\Common路径下的物理层处理文件phyHandling.c添加到FreeRTOS_TCP/Ports中。
将路径FreeRTOS-TCP\include和FreeRTOS-TCP\portable\NetworkInterface\include添加到工程头文件路径中 。
将FreeRTOS-Plus\Demo\FreeRTOS_Plus_TCP_Minimal_Windows_Simulator路径下的配置文件FreeRTOSIPConfig.h复制到User文件夹下,并在工程中添加到User分组中。
联网需要用到以太网物理层芯片,开发板采用LAN8720,用户需要编写LAN8720驱动函数。这里我们创建bsp_eth.c和bsp_eth.h两个文件,在文件里完成LAN8720驱动。将文件添加到工程中。使能ETH、DMA库。参考下面代码。
#include "bsp_eth.h"
void LAN8720_Init(void)
{
GPIO_InitTypeDef GPIO_Init;
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_ETH_CLK_ENABLE();
/*RMII接口引脚
ETH_MDIO -------------------------> PA2
ETH_MDC --------------------------> PC1
ETH_RMII_REF_CLK------------------> PA1
ETH_RMII_CRS_DV ------------------> PA7
ETH_RMII_RXD0 --------------------> PC4
ETH_RMII_RXD1 --------------------> PC5
ETH_RMII_TX_EN -------------------> PG11
ETH_RMII_TXD0 --------------------> PG13
ETH_RMII_TXD1 --------------------> PG14
ETH_RESET-------------------------> PD3*/
GPIO_Init.Mode=GPIO_MODE_AF_PP;
GPIO_Init.Pin=GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
GPIO_Init.Pull=GPIO_NOPULL;
GPIO_Init.Speed=GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_Init.Alternate=GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOA,&GPIO_Init);
GPIO_Init.Pin=GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
HAL_GPIO_Init(GPIOC,&GPIO_Init);
GPIO_Init.Pin=GPIO_PIN_11 | GPIO_PIN_14 | GPIO_PIN_13;
HAL_GPIO_Init(GPIOG,&GPIO_Init);
GPIO_Init.Mode=GPIO_MODE_OUTPUT_PP;
GPIO_Init.Pin=GPIO_PIN_3;
GPIO_Init.Pull=GPIO_NOPULL;
GPIO_Init.Speed=GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOD,&GPIO_Init);
//复位
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_3,GPIO_PIN_RESET);
vTaskDelay(100/portTICK_PERIOD_MS);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_3,GPIO_PIN_SET);
vTaskDelay(100/portTICK_PERIOD_MS);
HAL_NVIC_SetPriority(ETH_IRQn,6,0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
TCP协议里需要用到随机数,这个随机数需要用户提供。使能RNG库。随机数的创建参考下面代码。
#include "randomnum.h"
/* RNG handler declaration */
RNG_HandleTypeDef RngHandle;
static void Error_Handler()
{
while(1)
{
}
}
void RNG_init(void)
{
__HAL_RCC_RNG_CLK_ENABLE();
/*## Configure the RNG peripheral #######################################*/
RngHandle.Instance = RNG;
/* DeInitialize the RNG peripheral */
if (HAL_RNG_DeInit(&RngHandle) != HAL_OK)
{
/* DeInitialization Error */
Error_Handler();
}
/* Initialize the RNG peripheral */
if (HAL_RNG_Init(&RngHandle) != HAL_OK)
{
/* Initialization Error */
Error_Handler();
}
}
uint32_t Random_GetNumber(){
uint32_t num;
if (HAL_RNG_GenerateRandomNumber(&RngHandle, &num) != HAL_OK)
{
/* Random number generation error */
Error_Handler();
}
return num;
}
void getRandomNumTo(uint32_t * num){
if (HAL_RNG_GenerateRandomNumber(&RngHandle, num) != HAL_OK)
{
/* Random number generation error */
Error_Handler();
}
}
修改接口文件NetworkInterface.c下的初始化函数,引用TCP配置文件FreeRTOSIPConfig.h。引用LAN8720初始化函数。媒体接口改为RMII。引用头文件bsp_eth.h。删除PHY_AUTONEGO_COMPLETE宏定义。具体修改请参考如下代码。
BaseType_t xNetworkInterfaceInitialise( void )
{
HAL_StatusTypeDef hal_eth_init_status;
BaseType_t xResult;
if( xEMACTaskHandle == NULL )
{
#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
if( xTXDescriptorSemaphore == NULL )
{
xTXDescriptorSemaphore = xSemaphoreCreateCounting( ( UBaseType_t ) ETH_TXBUFNB, ( UBaseType_t ) ETH_TXBUFNB );
configASSERT( xTXDescriptorSemaphore );
}
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
/* Initialise ETH */
LAN8720_Init();
xETH.Instance = ETH;
xETH.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
xETH.Init.Speed = ETH_SPEED_100M;
xETH.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
xETH.Init.PhyAddress = 0;
xETH.Init.MACAddr = ( uint8_t *) ucMACAddress;
xETH.Init.RxMode = ETH_RXINTERRUPT_MODE;
/* using the ETH_CHECKSUM_BY_HARDWARE option:
both the IP and the protocol checksums will be calculated
by the peripheral. */
xETH.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
xETH.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
hal_eth_init_status = HAL_ETH_Init( &xETH );
/* Only for inspection by debugger. */
( void ) hal_eth_init_status;
/* Set the TxDesc and RxDesc pointers. */
xETH.TxDesc = DMATxDscrTab;
xETH.RxDesc = DMARxDscrTab;
/* Make sure that all unused fields are cleared. */
memset( &DMATxDscrTab, '\0', sizeof( DMATxDscrTab ) );
memset( &DMARxDscrTab, '\0', sizeof( DMARxDscrTab ) );
#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
/* Initialize Tx Descriptors list: Chain Mode */
DMATxDescToClear = DMATxDscrTab;
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
/* Initialise TX-descriptors. */
prvDMATxDescListInit();
/* Initialise RX-descriptors. */
prvDMARxDescListInit();
#if( ipconfigUSE_LLMNR != 0 )
{
/* Program the LLMNR address at index 1. */
prvMACAddressConfig( &xETH, ETH_MAC_ADDRESS1, ( uint8_t *) xLLMNR_MACAddress );
}
#endif
/* Force a negotiation with the Switch or Router and wait for LS. */
prvEthernetUpdateConfig( pdTRUE );
/* The deferred interrupt handler task is created at the highest
possible priority to ensure the interrupt handler can return directly
to it. The task's handle is stored in xEMACTaskHandle so interrupts can
notify the task when there is something to process. */
xTaskCreate( prvEMACHandlerTask, "EMAC", configEMAC_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xEMACTaskHandle );
} /* if( xEMACTaskHandle == NULL ) */
if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
{
xETH.Instance->DMAIER |= ETH_DMA_ALL_INTS;
xResult = pdPASS;
FreeRTOS_printf( ( "Link Status is high\n" ) ) ;
}
else
{
/* For now pdFAIL will be returned. But prvEMACHandlerTask() is running
and it will keep on checking the PHY and set ulPHYLinkStatus when necessary. */
xResult = pdFAIL;
FreeRTOS_printf( ( "Link Status still low\n" ) ) ;
}
/* When returning non-zero, the stack will become active and
start DHCP (in configured) */
return xResult;
}
TCP还有一些配置函数在接口文件中没有实现,需要用户自己实现。创建netInfoConfig.c和netInfoConfig.h文件用来实现TCP需要的一些接口配置函数。将文件添加在FreeRTOS_TCP/Ports组下。参考如下代码。
#include "netInfoConfig.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "string.h"
const uint8_t ucIPAddress[ 4 ] = { configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3 };
const uint8_t ucNetMask[ 4 ] = { configNET_MASK0, configNET_MASK1, configNET_MASK2, configNET_MASK3 };
const uint8_t ucGatewayAddress[ 4 ] = { configGATEWAY_ADDR0, configGATEWAY_ADDR1, configGATEWAY_ADDR2, configGATEWAY_ADDR3 };
const uint8_t ucDNSServerAddress[ 4 ] = { configDNS_SERVER_ADDR0, configDNS_SERVER_ADDR1, configDNS_SERVER_ADDR2, configDNS_SERVER_ADDR3 };
const uint8_t ucMACAddress[ 6 ] = { configMAC_ADDR0, configMAC_ADDR1, configMAC_ADDR2, configMAC_ADDR3, configMAC_ADDR4, configMAC_ADDR5 };
QueueHandle_t xPingReplyQueue;
UBaseType_t uxRand(){
return (UBaseType_t) getRandomNum();
}
const char *pcApplicationHostnameHook( void )
{
return mainHOST_NAME;
}
BaseType_t xApplicationDNSQueryHook( const char *pcName )
{
BaseType_t xReturn;
if( strcmp( pcName, pcApplicationHostnameHook() ) == 0 )
{
xReturn = pdPASS;
}
else if( strcmp( pcName, mainDEVICE_NICK_NAME ) == 0 )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
return xReturn;
}
void vApplicationPingReplyHook( ePingReplyStatus_t eStatus, uint16_t usIdentifier )
{
switch( eStatus )
{
case eSuccess:
xQueueSend( xPingReplyQueue, &usIdentifier, 10 / portTICK_PERIOD_MS );
break;
case eInvalidChecksum :
break;
case eInvalidData :
break;
}
}
void xPingReplyQueueCreate(void)
{
xPingReplyQueue = xQueueCreate( 20, sizeof( uint16_t ) );
}
BaseType_t vSendPing( const char *pcIPAddress )
{
uint16_t usRequestSequenceNumber, usReplySequenceNumber;
uint32_t ulIPAddress;
ulIPAddress = FreeRTOS_inet_addr( pcIPAddress );
if(xPingReplyQueue == NULL)
xPingReplyQueueCreate();
usRequestSequenceNumber = FreeRTOS_SendPingRequest( ulIPAddress, 8, 100 / portTICK_PERIOD_MS );
if( usRequestSequenceNumber == pdFAIL )
{
}
else
{
if( xQueueReceive( xPingReplyQueue, &usReplySequenceNumber, 200 / portTICK_PERIOD_MS ) == pdPASS )
{
if( usRequestSequenceNumber == usReplySequenceNumber )
{
}
}
}
return ulIPAddress;
}
BaseType_t IP_init( void )
{
return FreeRTOS_IPInit( ucIPAddress, ucNetMask, ucGatewayAddress, ucDNSServerAddress, ucMACAddress );
}
int lUDPLoggingPrintf( const char *fmt, ... )
{
return 0;
}
void vApplicationIPNetworkEventHook( eIPCallbackEvent_t eNetworkEvent )
{
uint32_t ulIPAddress, ulNetMask, ulGatewayAddress, ulDNSServerAddress;
char cBuffer[ 16 ];
static BaseType_t xTasksAlreadyCreated = pdFALSE;
FreeRTOS_printf( ( "vApplicationIPNetworkEventHook: event %ld\n", eNetworkEvent ) );
if( eNetworkEvent == eNetworkUp )
{
if( xTasksAlreadyCreated == pdFALSE )
{
#if( mainCREATE_UDP_LOGGING_TASK == 1 )
{
vUDPLoggingTaskCreate();
}
#endif
#if( ( mainCREATE_FTP_SERVER == 1 ) || ( mainCREATE_HTTP_SERVER == 1 ) )
{
/* Let the server work task now it can now create the servers. */
xTaskNotifyGive( xServerWorkTaskHandle );
}
#endif
#if( mainCREATE_UDP_CLI_TASKS == 1 )
{
vRegisterSampleCLICommands();
vRegisterTCPCLICommands();
vStartUDPCommandInterpreterTask( mainUDP_CLI_TASK_STACK_SIZE, mainUDP_CLI_PORT_NUMBER, mainUDP_CLI_TASK_PRIORITY );
}
#endif
xTasksAlreadyCreated = pdTRUE;
}
FreeRTOS_GetAddressConfiguration( &ulIPAddress, &ulNetMask, &ulGatewayAddress, &ulDNSServerAddress );
FreeRTOS_inet_ntoa( ulIPAddress, cBuffer );
FreeRTOS_printf( ( "IP Address: %s\n", cBuffer ) );
FreeRTOS_inet_ntoa( ulNetMask, cBuffer );
FreeRTOS_printf( ( "Subnet Mask: %s\n", cBuffer ) );
FreeRTOS_inet_ntoa( ulGatewayAddress, cBuffer );
FreeRTOS_printf( ( "Gateway Address: %s\n", cBuffer ) );
FreeRTOS_inet_ntoa( ulDNSServerAddress, cBuffer );
FreeRTOS_printf( ( "DNS Server Address: %s\n", cBuffer ) );
}
}
extern uint32_t ulApplicationGetNextSequenceNumber( uint32_t ulSourceAddress,
uint16_t usSourcePort,
uint32_t ulDestinationAddress,
uint16_t usDestinationPort )
{
( void ) ulSourceAddress;
( void ) usSourcePort;
( void ) ulDestinationAddress;
( void ) usDestinationPort;
return uxRand();
}
BaseType_t xApplicationGetRandomNumber(uint32_t* pulNumber)
{
*(pulNumber) = uxRand();
return pdTRUE;
}
修改网络配置文件FreeRTOSIPConfig.h,引用头文件stm32f4xx.h、在文件添加宏定义使能发送校验。在文件121、122行修改随机数配置、定义零拷贝发送、接收使能、定义MAC、IP、GATEWAY、DNS_SERVER、ECHO_SERVER地址和NET_MASK。定义USE_STM324xG_EVAL为0。参考如下代码。
// 使能发送校验
#define ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM 1
// 修改随机数为如下代码
extern uint32_t Random_GetNumber(void);
#define ipconfigRAND32() Random_GetNumber()
// 配置地址、硬件选择、收发模式
#define USE_STM324xG_EVAL 0
#define ipconfigZERO_COPY_RX_DRIVER ( 1 )
#define ipconfigZERO_COPY_TX_DRIVER ( 1 )
#define configMAC_ADDR0 0x00
#define configMAC_ADDR1 0x51
#define configMAC_ADDR2 0x52
#define configMAC_ADDR3 0x53
#define configMAC_ADDR4 0x54
#define configMAC_ADDR5 0x55
#define configIP_ADDR0 192
#define configIP_ADDR1 168
#define configIP_ADDR2 31
#define configIP_ADDR3 130
#define configGATEWAY_ADDR0 0
#define configGATEWAY_ADDR1 0
#define configGATEWAY_ADDR2 0
#define configGATEWAY_ADDR3 0
#define configDNS_SERVER_ADDR0 0
#define configDNS_SERVER_ADDR1 0
#define configDNS_SERVER_ADDR2 0
#define configDNS_SERVER_ADDR3 0
#define configNET_MASK0 0
#define configNET_MASK1 0
#define configNET_MASK2 0
#define configNET_MASK3 0
#define configECHO_SERVER_ADDR0 192
#define configECHO_SERVER_ADDR1 168
#define configECHO_SERVER_ADDR2 31
#define configECHO_SERVER_ADDR3 237
注释FreeRTOSConfig.h文件下的#define xPortSysTickHandler SysTick_Handler。在FreeRTOS\portable\RVDS\ARM_CM4F路径下创建port.h文件,在文件中加入函数声明void xPortSysTickHandler( void );。在clock.c文件中引用port.h头文件并加入如下代码。
void SysTick_Handler()
{
HAL_IncTick();
xPortSysTickHandler();
}
在main.c文件中引用头文件netInfoConfig.h,在main函数中调用初始化函数。参考如下代码。
#include "bsp_clock.h"
#include "bsp_randomnum.h"
#include "netInfoConfig.h"
int main(void)
{
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
CLOCLK_Init();
RNG_init();
IP_init();
vTaskStartScheduler();
}
工程编译,出现大量错误,报错类型是类型未定义,在FreeRTOS_IP.h文件中引用FreeRTOS.h和list.h头文件。
编译工程,有13个错误。这里是要提供两个编译器内置的命令以取消结构体自动字节对齐,如果用的IDE是keil,那么默认编译器是armcc,需要pack_struct_start.h文件内添加#pragma pack(1),并在pack_struct_end.h文件内添加#pragma pack(),在FreeRTOS-TCP\include路径下创建这两个文件,并将代码添加进去。再次编译。
工程编译,出现大量错误,报错类型是说明符的无效组合,在报错的结构体后面加上英文“;”。再次编译。
工程编译,出现大量警告,警告类型是已弃用声明,在函数定义和声明的地方将参数加入void。
修改stm32f4xx_hal_eth.c文件下函数HAL_ETH_IRQHandler()里的else if,改成if。参考如下代码。
void HAL_ETH_IRQHandler(ETH_HandleTypeDef *heth)
{
/* Frame received */
if (__HAL_ETH_DMA_GET_FLAG(heth, ETH_DMA_FLAG_R))
{
/* Receive complete callback */
HAL_ETH_RxCpltCallback(heth);
/* Clear the Eth DMA Rx IT pending bits */
__HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_R);
/* Set HAL State to Ready */
heth->State = HAL_ETH_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(heth);
}
/* Frame transmitted */
if (__HAL_ETH_DMA_GET_FLAG(heth, ETH_DMA_FLAG_T))
{
/* Transfer complete callback */
HAL_ETH_TxCpltCallback(heth);
/* Clear the Eth DMA Tx IT pending bits */
__HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_T);
/* Set HAL State to Ready */
heth->State = HAL_ETH_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(heth);
}
/* Clear the interrupt flags */
__HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_NIS);
/* ETH DMA Error */
if(__HAL_ETH_DMA_GET_FLAG(heth, ETH_DMA_FLAG_AIS))
{
/* Ethernet Error callback */
HAL_ETH_ErrorCallback(heth);
/* Clear the interrupt flags */
__HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_FLAG_AIS);
/* Set HAL State to Ready */
heth->State = HAL_ETH_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(heth);
}
}