[STM32F4][步步深入学网络003]FreeRTOS移植

随着协议种类的增多及复杂程度的增加,像上小节手动解析数据包的方式工作量会大量增加,所以需要一个规范的、完整的协议栈(例如LWIP,uip等)来处理数据包,这样,用户只关心应用层的数据即可;另外,LWIP虽然在无需操作系统也可以运行,但性能较差,API接口使用也受限制,所以还需要一个RTOS配合使用,相得益彰;所以本小节与下小节进行移植FreeRTOS与LWIP,后续小节再继续进行网络协议的介绍。

本小节主要介绍FreeRTOS的移植

移植过程

FREERTOS官网下载地址:https://www.freertos.org/,这里选择当前最新的稳定版本10.3.1下载,
官方已经给出了大量的BSP以及说明文档,移植过程相对比较简单,主要步骤如下:

(1)打开源码压缩包,进入FreeRTOSv10.3.1/FreeRTOS/Source目录,复制此目录所有文件在这里插入图片描述
(2)建立新的源码目录结构,将复制的文件放进FreeRTOS目录,整个移植目录树结构如下:

├─application (应用代码)
├─board(板级代码)
│  ├─inc
│  └─src
├─chip(芯片级代码,芯片库文件)
│  ├─CMSIS
│  └─STM32F4xx_HAL_Driver
├─console(调试输入输出代码)
├─FreeRTOS(FreeRTOS源码)
│  ├─include
│  └─portable
│      ├─Common
│      ├─MemMang
│      └─RVDS
│          └─ARM_CM4F
├─hw_driver(硬件驱动)
└─mdk(Keil5 MDK工程文件)

(3)删除FreeRTOS/portable/下无关目录,只留Common、MemMang、RVDS/ARM_CM4F文件夹
(4)从FreeRTOSv10.3.1/FreeRTOS/Demo/CORTEX_M4F_STM32F407ZG-SK/文件夹中复制一份FreeRTOSConfig.h文件至刚刚新建的源码目录中的“application”文件夹中,并重新根据需要配置,参考配置如下:


#include <stdint.h>
extern volatile uint32_t FreeRTOS_RuntimerCount;

#define configUSE_PREEMPTION						1
#define configUSE_TIME_SLICING						1

#define configUSE_PORT_OPTIMISED_TASK_SELECTION		1
#define configUSE_TICKLESS_IDLE						0

#define configUSE_IDLE_HOOK							1
#define configUSE_TICK_HOOK							1
#define configCPU_CLOCK_HZ							( 168000000 )
#define configTICK_RATE_HZ							( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES						( 32 )
#define configMINIMAL_STACK_SIZE					( ( unsigned short ) 130 )
#define configMAX_TASK_NAME_LEN						( 16 )

#define configUSE_16_BIT_TICKS						0
#define configIDLE_SHOULD_YIELD						1
#define configUSE_MUTEXES							1
#define configQUEUE_REGISTRY_SIZE					8
#define configCHECK_FOR_STACK_OVERFLOW				2
#define configUSE_RECURSIVE_MUTEXES					1
#define configUSE_MALLOC_FAILED_HOOK				1
#define configUSE_APPLICATION_TASK_TAG				0
#define configUSE_COUNTING_SEMAPHORES				1

#define configGENERATE_RUN_TIME_STATS				1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 
#define portGET_RUN_TIME_COUNTER_VALUE()  			FreeRTOS_RuntimerCount

#define configUSE_TRACE_FACILITY					1
#define configUSE_STATS_FORMATTING_FUNCTIONS		1 


/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 						0
#define configMAX_CO_ROUTINE_PRIORITIES 			( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS							1
#define configTIMER_TASK_PRIORITY					( 2 )
#define configTIMER_QUEUE_LENGTH					10
#define configTIMER_TASK_STACK_DEPTH				( configMINIMAL_STACK_SIZE * 2 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet					1
#define INCLUDE_uxTaskPriorityGet					1
#define INCLUDE_vTaskDelete							1
#define INCLUDE_vTaskCleanUpResources				1
#define INCLUDE_vTaskSuspend						1
#define INCLUDE_vTaskDelayUntil						1
#define INCLUDE_vTaskDelay							1
#define INCLUDE_uxTaskGetStackHighWaterMark 		1

#define configPRIO_BITS       						__NVIC_PRIO_BITS

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY		0xf

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	1

#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }	

#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

以上各个配置参数的意义,请参考官方说明文档,有详细的说明以及如何选择、使用,还是十分方便的。

(5)建立一个新的MDK工程
(6)将前两节的原文件添加到工程中(STM32库文件等)
(7)编写、修改驱动代码、应用代码

建好的工程如下:
在这里插入图片描述

应用代码部分,目前还是以串口输出“hello world”为demo示例,建立10个任务,每个任务各自输出“hello world”,并带上自己的任务名称。

#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "console.h"
#include "string.h"
#include "board.h"
#include <stdio.h>

/* 所有任务的执行代码 */
void TestTask( void *pvParameters )
{
    while(1)
    {
    
        LocalPrintf("[%08d] hello world, I'm task -- [%s]!\r\n",
        	HAL_GetTick(),pcTaskGetName(NULL));
        	
        vTaskDelay(1000);
    }
}

/* 初始化任务 */
void InitTask( void *pvParameters )
{
    char* task_name;
    
    /* 硬件初始化 */
    BoardInit();
    
    task_name = pvPortMalloc(configMAX_TASK_NAME_LEN + 1);

    /* 创建10个测试任务,任务名称为Test0~Test9 */
    for(int i = 0; i < 10 ; i++)
    {
        snprintf(task_name,configMAX_TASK_NAME_LEN,"Test%d",i);
        xTaskCreate( TestTask, task_name, 1024, ( void * ) i, tskIDLE_PRIORITY+3, NULL );
    }
    
    vPortFree(task_name);

	/* 初始化任务完成,删除自身任务 */
    vTaskDelete(NULL);
}

结果如下,可以看到10个任务同时运行,达到了预期。
在这里插入图片描述

注意事项:

(1)由于FreeRTOS会初始化SysTick,故STM32 中HAL库中关于Systick的初始化应当去掉,只需在board.c中重写为空定义即可:

HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    return HAL_OK;
}

(2)动态内存分配采用了heap5.c,使用前需要类似如下定义:

/* Heap Regions Definition for heap_5.c */
const HeapRegion_t xHeapRegions[] =
{
  	{ ( uint8_t * ) 0x10000000, 0x10000 },
  	{ NULL, 0 }
};

在调度操作系统之前作如下调用:
/* Heap Regions Init */
vPortDefineHeapRegions( xHeapRegions );
   

这里的0x10000000,0x10000是STM32F407上的另一片内存的地址及大小,可以在工程设置中查到
在这里插入图片描述

(3)由于很多STM32中的HAL库很多地方用到了SysTick的相关函数,而SysTick中断函数为FREERTOS提供,为了兼容,需要在滴答节拍的钩子函数中添加原来的HAL_IncTick()函数即可。
如下:

void vApplicationTickHook( void )
{
    HAL_IncTick();
}


(4)有了多任务的存在,以前的LocalPrintf函数就需要添加保护机制了,否则多个任务同时调用此函数会资源竞争。这里使用了FreeRTOS的二值信号量,如下:

static SemaphoreHandle_t xConsolePrintfSemaphore = NULL;
__align(8) static char  buf_str[800];

void  LocalPrintf (char *format, ...)
{
    va_list   v_args;
    
    if(xConsolePrintfSemaphore == NULL)
    {
        vSemaphoreCreateBinary( xConsolePrintfSemaphore );
    }

    /* 获取资源 */
    xSemaphoreTake( xConsolePrintfSemaphore, portMAX_DELAY );
    
    va_start(v_args, format);
   (void)vsnprintf((char       *)&buf_str[0],
                   (size_t      ) sizeof(buf_str),
                   (char const *) format,
                                  v_args);
    va_end(v_args);
    ConsoleSendString(buf_str);

    /* 输出完毕,释放资源*/
    xSemaphoreGive(xConsolePrintfSemaphore);
}


(5)有了多任务的存在,就需要尽可能避免使用原地踏步等待(浪费大量的CPU资源),而是等待时候挂起当前任务,释放主动权给FreeRTOS,直到等待的事件、信号量、标志出现,重新唤醒任务。例如串口输出部分,就不能像之前面两节的裸机程序那样:


int ConsoleSendString(char* s)
{
    USART1_TX_IdleFlag = 0;
    HAL_UART_Transmit_DMA(&huart1,(uint8_t *)s,strlen(s));

	
    while(USART1_TX_IdleFlag == 0)//这里会占用大量CPU资源
    {
    }
    
    return 0;
}

应当做如下处理:

static SemaphoreHandle_t xUART_SendSemaphore = NULL;

int ConsoleSendString(char* s)
{
    if(xUART_SendSemaphore == NULL)
    {
        vSemaphoreCreateBinary( xUART_SendSemaphore );
        xSemaphoreTake( xUART_SendSemaphore, 0 );
    }
    HAL_UART_Transmit_DMA(&huart1,(uint8_t *)s,strlen(s));

    /* 释放CPU,直到输出完成,占用很少的CPU资源 */
    xSemaphoreTake( xUART_SendSemaphore, portMAX_DELAY );

    return 0;
}


void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    BaseType_t xHigherPriorityTaskWoken;

   /* 发送信号量,标识串口发送完成,唤醒正在等待的任务*/
	xSemaphoreGiveFromISR( xUART_SendSemaphore,&xHigherPriorityTaskWoken);
    if( xHigherPriorityTaskWoken != pdFALSE )
    {
        // We can force a context switch here.  Context switching from an
        // ISR uses port specific syntax.  Check the demo task for your port
        // to find the syntax required.
    }
    
}

(6)本节源码的功能结构说明如下:
【main.c】只用来建立初始化任务,并启动FreeRTOS调度器(包括启动前的必要准备工作)
【init.c 】硬件初始化、建立应用任务等等,放在初始化任务中,初始化完成后,删除初始化任务
【board.c】主要用于存放HAL库生成的原始外设初始化代码

附本节源码列表:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值