本项目是基于FreeRTOS项目的STM32CubeMX开发方式,说明了具体配置与相关参数,以及mdk使用,裸机也可以参考本配置。
1、新建项目
2、选择芯片型号
3、配置时钟
RCC 设置,选择 HSE(外部高速时钟) 和LSE(低速时钟)为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
4、时钟配置,去Clock Configuration配置RCC时钟为72MHZ,外部低速时钟LSE主要和RTC有关,看情况配置,使用的话需要去使能RTC,使能回去选择时钟为LSE时钟线路 32.768Khz.
注意RTC的配置,可以选择BCD和16进制
5、配置调试模式,不配置听说第一次烧录代码厚,jlink不能识别设备了
SYS 设置,选择 Debug 为 Serial Wire,根据情况选择自己的仿真器
6、在 System Core 中选择 SYS ,对 Timebase Source 进行设置,选择 TIM1 作为HAL库的时基(除了 SysTick 外都可以),如果是裸机的话可以选择systick时钟,但目前我要跑系统
在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:
HAL的时基,SYS Timebase Source
OS的时基(仅在使用OS的情况下才考虑)
而这些 “时基” 该去如何维护,主要分为两种情况考虑:
1、裸机运行:
可以通过 SysTick(滴答定时器)或 (TIMx)定时器 的方式来维护 SYS Timebase Source,也就是HAL库中的 uwTick,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler() 中断服务函数中来维护。
2、带OS运行:
前面提到的 SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。
在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。
使用FreeRTOS的时候,不要使用 SysTick(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用
7、FreeRTOS配置
在 Middleware 中选择 FREERTOS 设置,并选择 CMSIS_V1 接口版本
两个标准接口区别,CMSIS是一种接口标准,目的是屏蔽软硬件差异以提高软件的兼容性。RTOS v1使得软件能够在不同的实时操作系统下运行(屏蔽不同RTOS提供的API的差别),而RTOS v2则是拓展了RTOS v1,兼容更多的CPU架构和实时操作系统。因此我们在使用时可以根据实际情况选择,如果学习过程中使用STM32F1、F4等单片机时没必要选择RTOS v2,更高的兼容性背后时更加冗余的代码,理解起来比较困难。
mutexes创建互斥量
events 创建事件
FreeRTOS heap usage FreeRTOS堆使用情况
user constants 用户常量
tasks and queues 任务和队列
timers and semaphores 定时器任务和队列
config parameters 配置参数
include parameters 头文件参数
advanced sttings 高级设置
config parameters 配置参数
{
kernel settings 内核配置
{
USE_PREEMPTION: Enabled:RTOS使用抢占式调度器;Disabled:RTOS使用协作式调度器(时间片)。
TICK_RATE_HZ: 值设置为1000,即周期就是1ms。RTOS系统节拍中断的频率,单位为HZ。
MAX_PRIORITIES: 可使用的最大优先级数量。设置好以后任务就可以使用从0到(MAX_PRIORITIES - 1)的优先级,其中0位最低优先级,(MAX_PRIORITIES - 1)为最高优先级。
MINIMAL_STACK_SIZE: 设置空闲任务的最小任务堆栈大小,以字为单位,而不是字节。如该值设置为128 Words,那么真正的堆栈大小就是 128*4 = 512 Byte。
MAX_TASK_NAME_LEN: 设置任务名最大长度。
IDLE_SHOULD_YIELD: Enabled 空闲任务放弃CPU使用权给其他同优先级的用户任务。
USE_MUTEXES: 为1时使用互斥信号量,相关的API函数会被编译。
USE_RECURSIVE_MUTEXES: 为1时使用递归互斥信号量,相关的API函数会被编译。
USE_COUNTING_SEMAPHORES: 为1时启用计数型信号量, 相关的API函数会被编译。
QUEUE_REGISTRY_SIZE: 设置可以注册的队列和信号量的最大数量,在使用内核调试器查看信号量和队列的时候需要设置此宏,而且要先将消息队列和信号量进行注册,只有注册了的队列和信号量才会在内核调试器中看到,如果不使用内核调试器的话次宏设置为0即可。
USE_APPLICATION_TASK_TAG: 为1时可以使用vTaskSetApplicationTaskTag函数。
ENABLE_BACKWARD_COMPATIBILITY: 为1时可以使V8.0.0之前的FreeRTOS用户代码直接升级到V8.0.0之后,而不需要做任何修改。
USE_PORT_OPTIMISED_TASK_SELECTION: FreeRTOS有两种方法来选择下一个要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU自带的硬件指令来实现。STM32有计算前导零指令吗,所以这里强制置1。
USE_TICKLESS_IDLE: 置1:使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行。假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用ISP下载办法解决。
USE_TASK_NOTIFICATIONS: 为1时使用任务通知功能,相关的API函数会被编译。开启了此功能,每个任务会多消耗8个字节。
RECORD_STACK_HIGH_ADDRESS: 为1时栈开始地址会被保存到每个任务的TCB中(假如栈是向下生长的)。
}
Memory management settings:内存管理设置
{
Memory Allocation: Dynamic/Static 支持动态/静态内存申请
TOTAL_HEAP_SIZE: 设置堆大小,如果使用了动态内存管理,FreeRTOS在创建 task, queue, mutex, software timer or semaphore的时候就会使用heap_x.c(x为1~5)中的内存申请函数来申请内存。这些内存就是从堆ucHeap[configTOTAL_HEAP_SIZE]中申请的。
Memory Management scheme: 内存管理策略 heap_4。
}
TOTAL_HEAP_SIZE的配置可以参考http://www.manongjc.com/detail/59-ethaltlwtgyvjls.html
Hook function related definitions: 钩子功能相关定义
{
USE_IDLE_HOOK: 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子。
USE_TICK_HOOK: 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子。
USE_MALLOC_FAILED_HOOK: 使用内存申请失败钩子函数。
CHECK_FOR_STACK_OVERFLOW: 大于0时启用堆栈溢出检测功能,如果使用此功能用户必须提供一个栈溢出钩子函数,如果使用的话此值可以为1或者2,因为有两种栈溢出检测方法。
}
Run time and task stats gathering related definitions:运行时和任务统计数据收集相关定义:
{
GENERATE_RUN_TIME_STATS: 启用运行时间统计功能。
USE_TRACE_FACILITY: 启用可视化跟踪调试。
USE_STATS_FORMATTING_FUNCTIONS: 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数prvWriteNameToBuffer()、vTaskList()、vTaskGetRunTimeStats()。
}
Co-routine related definitions:协同例程相关定义:
{
USE_CO_ROUTINES: 启用协程。
MAX_CO_ROUTINE_PRIORITIES: 协程的有效优先级数目。
}
Software timer definitions:软件定时器
{
USE_TIMERS: 启用软件定时器。
}
Interrupt nesting behaviour configuration:中断嵌套行为配置:
{
LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中断最低优先级。
LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系统可管理的最高中断优先级。
}
}
使用软件定时器的话,要在配置里面使能,然后去timers and semaphores 定时器任务和队列创建
Timer Name: 定时器名称
Callback: 回调函数名称
Type: 定时器类型,osTimerPeriodic周期定时器,osTimerOnce单次定时器
Code Generation Option: 代码生成选项
Parameter: 回调函数形参,不用的时候配置为0或NULL即可
Allocation: 分配方式:Dynamic 动态内存创建
Conrol Block Name: 控制块名称
8、project Manager进入项目管理
分别是项目名称,项目地址,程序结构、工具链文件夹位置、编译IDE(选择MDK版本)
Stack栈的大小为:0x400(1024Byte),Heap堆的大小为:0x200(512Byte)。
这也是为什么一个基础的工程编译后,RAM的空间也占用了1.6K左右的原因,因为堆栈的空间均分配在RAM中,可在编译的map文件中查看RAM资源占用的情况。
若工程中使用的局部变量较多,定义的数据长度较大时,若不调整栈的空间大小,则会导致程序出现栈溢出,程序运行结果与预期的不符或程序跑飞。这时我们就需要手动的调整栈的大小。
当工程中使用了malloc动态分配内存空间时,这时分配的空间就为堆的空间。所以若默认的堆空间大小不满足工程需求时,就需要手动调整堆空间的大小。
无论是前后台或者是基于RTOS的系统都可能在调用C库函数时存在线程安全Thread-Safe问题,例如下图对malloc()函数的调用。该类问题出现时,公用资源例如RAM数据出现冲突而应用程序仍能在一段正常运行后才出现问题。对工程师而言,难以调试,也难以查找到根本原因。STM32CubeMX针对EWARM, MDK-ARM和STM32CubeIDE工具链提供了对应的Thread-Safe解决策略,可以直接配置完成。
将所有使用过的库复制到项目文件夹中
只拷贝需要的库文件
在工具链项目配置文件中添加必要的库文件作为参考
将外围设备初始化生成为每个外围设备的一对.c/.h’文件
重新生成时备份以前生成的文件
重新生成时保留用户代码
不重新生成时删除以前生成的文件
将所有空闲引脚设置为模拟引脚(以优化功耗
启用完全断言
选择模板以生成自定义代码即可
9、首先 ,假设我们相使用LED灯,我们建立自己的用户驱动文件USer,直接在user里面放置LED.C和LED.h文件即可
10、把刚才的序幕加载到工程里
11、我们可以把freertos.文件的驱动直接复制到LED.C即可,
注意要去掉__weak弱定义修饰符
12、在LED.加上头文件#include “cmsis_os.h”
#include “main.h”,这个任务就成功移植到其他文件了,这样做的好处是方便任务编写管理,你任务很多写在一起会很难查看代码。再就是下次配置STM32CubeMX 他是不会删除LED这些文件的。
需要注意的是如果在CubeMX生成的文件里编写自己的代码需要注意位置哦,否则后面再次使用软件配置会导致编写的变量和代码删除,最好使能工程备份,当然你工程建立后不在使用stm32cubemx可以不按照下面规范定义数据和编写代码。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */ 用户头文件定义区域
#include "LED/LED.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */ 用户数据类型定义区域
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */ 用户宏定义区域
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */宏指令区域
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */变量区域
/* USER CODE END PV */
反正只要清楚代码和变量写在这个结构内部就不会清楚掉就可以了
/* USER CODE BEGIN xxx*/
/* USER CODE END xxx*/