随着协议种类的增多及复杂程度的增加,像上小节手动解析数据包的方式工作量会大量增加,所以需要一个规范的、完整的协议栈(例如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库生成的原始外设初始化代码
附本节源码列表: