STM32上的FreeRTOS移植指南(一):从零开始
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
碎碎念:
最近搞电池把GD32E230烧坏了- -整个芯片和LDO都烧坏了- -嘉立创什么时候再免费派券让我白嫖一个开发板?
主要内容:
用KEIL5移植FreeRTOS到STM32F103C8T6上,因为我看网上好多教程都是用CUBE直接生成的,但是好多国产芯片还没有能使用CUBE,虽然也有逃课的办法但是我觉得也不太好用,而且在GITHUB上移植大多数也是需要对文件修改的,所以就当是个学习机会啦。
后续也会写在国产芯片上移植FreeRTOS的办法,一步步来尽量做到通俗易懂。
一、找资料
我这次用的开发板是立创的STM32F103C8T6,这个板子很多人用而且也很便宜(我白嫖到的,谢谢嘉立创)。
但是芯片不是重点,基本上STM下面的芯片都非常兼容FreeRTOS,更简单的办法是用CUBEIDE直接生成工程文件。
这篇日志纯粹是能在其他芯片上也能移植FreeRTOS打基础的。
好啦,闲话不多说,直接开始找资料。
1.任意教程提供的点灯程序
众所周知,FreeRTOS是基于滴答定时器的高级应用,所以滴答定时器尤其重要,在这个实验里我们要实现灯闪烁的效果。
链接: 立创提供的STM32F103资料
2.FreeRTOS源码
链接: FreeRTOS
二、创建工程模板
请在创建FreeRTOS工程模板之前确保你的点灯程序是可以跑起来的。
下面列出来的就是我们需要多加的文件(有些工程是有it.c的,这是一个关于中断的文件。)
下载了FreeRTOS之后,我们需要找到FreeRTOS里面的Source,具体路径为:FreeRTOSv202212.01\FreeRTOSv202212.01\FreeRTOS\Source
把不是.c后缀的文件删掉,再把这个文件夹移植到你的项目工程下,改名成FreeRTOS方便管理。
打开里面的portable文件夹,把除了KEIL,MemMang和RVDS的其他文件都删掉。
然后在KEIL里打开项目,在项目里加入头文件
FreeRTOS\include
FreeRTOS\portable\RVDS\ARM_CM3
module
第一个文件是FreeRTOS依赖的头文件,每个人都一样要加的。
第二个文件的选择是基于你的芯片的ARM架构,数据手册上的第一页就会写,所以移植的时候也要特别注意是不是FreeRTOS写好的架构。有些芯片是不太支持的。(或者说大佬自己改)
FreeRTOS目前支持的架构:
第三个文件是中断函数相关的,大多数工程都会把他们放出来,但是立创这次没有,咱也不敢问,默默补上就行。中断函数非常重要,一定要加到工程里。(虽然里面都是空的,自己补写也是可以的。)
再将对应的文件加入进来,如下:
三、配置FreeRTOS
这里我强烈建议各位去看官网的资料,实际上并没有想象的那么复杂,而且官网讲的非常详细!
链接: FreeRTOSConfig介绍
而且大多数的配置文件只需要修改很少很少地方。下面是我写的,可以复制过去,要改的地方我都写了备注的。
/*
* FreeRTOS Kernel V10.2.0
* Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
/* Ensure stdint is only used by the compiler, and not the assembler. */
/* 确保 stdint 只被编译器使用,而不是汇编器使用。 */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
// FreeRTOS 配置选项
#define configUSE_PREEMPTION 1 // 使用抢占式调度
#define configUSE_IDLE_HOOK 0 // 不使用空闲钩子函数
#define configUSE_TICK_HOOK 0 // 不使用滴答钩子函数
#define configCPU_CLOCK_HZ ( SystemCoreClock ) // 设置 CPU 时钟频率,这里可以根据数据手册写死,也可以像我一样用内核文件定义
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) // 滴答频率设置为 1000Hz,这个官方示例给的
#define configMAX_PRIORITIES ( 5 ) // 最大优先级数目,根据需要修改
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 ) // 最小任务堆栈大小,根据需要修改,每个代表4个字节,即:130*4=520个字节
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 8 * 1024 ) ) // 堆大小设置为 8KB,,根据需要修改
#define configMAX_TASK_NAME_LEN ( 16 ) // 任务名字的最大长度
#define configUSE_TRACE_FACILITY 1 // 启用追踪功能
#define configUSE_16_BIT_TICKS 0 // 使用 16 位的滴答计数器或者32位的滴答计时器
#define configIDLE_SHOULD_YIELD 1 // 空闲任务是否应该放弃 CPU,这里必须设置1
#define configUSE_MUTEXES 1 // 启用互斥量
#define configQUEUE_REGISTRY_SIZE 8 // 队列注册表的大小
#define configCHECK_FOR_STACK_OVERFLOW 0 // 不启用堆栈溢出检查
#define configUSE_RECURSIVE_MUTEXES 1 // 启用递归互斥量
#define configUSE_MALLOC_FAILED_HOOK 0 // 不使用内存分配失败钩子函数
#define configUSE_APPLICATION_TASK_TAG 0 // 不使用任务标签
#define configUSE_COUNTING_SEMAPHORES 1 // 启用计数信号量
#define configGENERATE_RUN_TIME_STATS 0 // 不生成运行时统计信息
/* 协程定义 */
#define configUSE_CO_ROUTINES 0 // 不使用协程
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) // 最大协程优先级数
/* 软件定时器定义 */
#define configUSE_TIMERS 1 // 启用软件定时器
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) // 定时器任务的优先级
#define configTIMER_QUEUE_LENGTH 5 // 定时器队列长度
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) // 定时器任务堆栈深度
/* 包含 API 函数的设置,根据自己需求启用 */
#define INCLUDE_vTaskPrioritySet 1 // 启用 vTaskPrioritySet 函数
#define INCLUDE_uxTaskPriorityGet 1 // 启用 uxTaskPriorityGet 函数
#define INCLUDE_vTaskDelete 1 // 启用 vTaskDelete 函数
#define INCLUDE_vTaskCleanUpResources 1 // 启用 vTaskCleanUpResources 函数
#define INCLUDE_vTaskSuspend 1 // 启用 vTaskSuspend 函数
#define INCLUDE_vTaskDelayUntil 1 // 启用 vTaskDelayUntil 函数
#define INCLUDE_vTaskDelay 1 // 启用 vTaskDelay 函数
#define INCLUDE_xTaskGetSchedulerState 0
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 0
#define INCLUDE_uxTaskGetStackHighWaterMark2 0
#define INCLUDE_xTaskGetIdleTaskHandle 0
#define INCLUDE_eTaskGetState 0
#define INCLUDE_xEventGroupSetBitFromISR 0
#define INCLUDE_xTimerPendFunctionCall 0
#define INCLUDE_xTaskAbortDelay 0
#define INCLUDE_xTaskGetHandle 0
#define INCLUDE_xTaskResumeFromISR 0
/* Cortex-M 特定定义 */
#ifdef __NVIC_PRIO_BITS
/* 当使用 CMSIS 时,__NVIC_PRIO_BITS 会被指定 */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 个优先级 */
#endif
/* 可用于调用 "set priority" 函数的最低中断优先级 */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
/* 可由任何中断服务例程使用的最高中断优先级
(较高的优先级具有较低的数值)。不要从比这更高优先级
的中断中调用 FreeRTOS API 函数!*/
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* 内核端口层自身使用的中断优先级。适用于所有 Cortex-M 端口,
不依赖于任何特定的库函数 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY 不能设置为零 !!!!
参见 http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* 不依赖 assert.h 头文件提供的正常 assert() 语义 */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
/* 定义将 FreeRTOS 端口中断处理程序映射到它们的 CMSIS 标准名称 */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
//#define xPortSysTickHandler SysTick_Handler
#endif /* FREERTOS_CONFIG_H */
将配置文件放到Include文件夹即可。
四、小修小补
这样FreeRTOS基本上就配置好了,我们先来写一个点灯函数进行测试:
/*
**GPIO口初始化
*/
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
}
/*
**创建灯闪烁任务
*/
void vTaskLED(void *pvParameters)
{
(void) pvParameters;
while(1)
{
// 切换 LED 状态
GPIO_WriteBit(GPIOC, GPIO_Pin_13, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
// 延时 1 秒
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void)
{
// 配置系统时钟
board_init();
// 初始化 LED
LED_Init();
// 创建 LED 闪烁任务
xTaskCreate(vTaskLED, "LED Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果启动调度器失败,将会执行到这里
while(1);
}
这个时候编译会出错,提示如下:
这是因为在配置文件中我们对两个函数进行了映射,在中断文件中也有映射。
/* 定义将 FreeRTOS 端口中断处理程序映射到它们的 CMSIS 标准名称 */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
我们不想用原来的,就需要到it.c文件中把这两个函数注释掉。
目前到这里我就没报错问题了,现在将程序烧录到单片机上,现象是:程序烧录之后单片机没有正常亮灯,调试会发现执行到这里就会卡死。
vTaskDelay(pdMS_TO_TICKS(1000));
这还是因为中断的问题,FreeRTOS用了滴答定时器,我们要在滴答定时器中断时调用的函数里调用FreeRTOS的函数。在it.c文件里找到修改就好啦。
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
xPortSysTickHandler();
}
再进行烧录,就能在单片机上看到灯闪烁了。
总结
这个DEMO我其实已经做了两次- -没想到第二次还是花了我两个多小时的时间,虽然里面有一半是因为在写文章。但是还是好累啊- -一定要保存好DEMO不要天天搭建环境啊- –
文件放在这里,需要自提哈。
链接:https://pan.baidu.com/s/1L0MrklDR94agIlbDJnlVqA?pwd=ki9e
提取码:ki9e
作者:YUki又叫6777