FreeRTOS 移植保姆级教程 — MSPM0G3507 + Keil
准备工作
你需要的东西
Keil MDK(已装好)
TI MSPM0 SDK(已装好)
FreeRTOS 源码包(去 GitHub 搜 FreeRTOS-Kernel 下载 V11.3.0)
你的 MSPM0G3507 Keil 工程(已经有了)
工程目录确认
打开你的工程文件夹,确认有这些目录:
你的项目文件夹/
├── empty/ ← 你的代码在这里
│ ├── keil/ ← Keil 工程文件在这里
│ ├── DriveLib/
│ ├── OLED/
│ └── System/
└── FreeRTOS-Kerne
├── source/ ← TI SDK
└── syscfg.bat第一步:把 FreeRTOS 源码复制进工程
从下载好的 FreeRTOS-Kernel 文件夹里复制以下文件:
① 复制内核源文件
FreeRTOS-Kernel/tasks.c
→ 你的工程/empty/FreeRTOS/src/tasks.c
FreeRTOS-Kernel/queue.c
→ 你的工程/empty/FreeRTOS/src/queue.c
FreeRTOS-Kernel/list.c
→ 你的工程/empty/FreeRTOS/src/list.c
FreeRTOS-Kernel/timers.c
→ 你的工程/empty/FreeRTOS/src/timers.c
FreeRTOS-Kernel/event_groups.c
→ 你的工程/empty/FreeRTOS/src/event_groups.c
FreeRTOS-Kernel/stream_buffer.c
→ 你的工程/empty/FreeRTOS/src/stream_buffer.c
FreeRTOS-Kernel/croutine.c
→ 你的工程/empty/FreeRTOS/src/croutine.c② 复制头文件
FreeRTOS-Kernel/include/ 里面的全部 .h 文件 → 你的工程FreeRTOS/include/③ 复制移植层(选 GCC 的,不要选 RVDS)
FreeRTOS-Kernel/portable/GCC/ARM_CM0/port.c
→ 你的工程/FreeRTOS/portable/ARM_CM0/port.c
FreeRTOS-Kernel/portable/GCC/ARM_CM0/portasm.c
→ 你的工程FreeRTOS/portable/ARM_CM0/portasm.c
FreeRTOS-Kernel/portable/GCC/ARM_CM0/portasm.h
→ 你的工程FreeRTOS/portable/ARM_CM0/portasm.h
FreeRTOS-Kernel/portable/GCC/ARM_CM0/portmacro.h
→ 你的工程FreeRTOS/portable/ARM_CM0/portmacro.h④ 复制内存管理文件
FreeRTOS-Kernel/portable/MemMang/heap_4.c
→ 你的工程FreeRTOS/portable/heap_4.c完成后你的工程目录会多出一个 FreeRTOS/ 文件夹:
你的项目文件夹/
│ ├── FreeRTOS/ ← 新增
│ │ ├── src/ ← tasks.c, queue.c 等 7 个文件
│ │ ├── include/ ← FreeRTOS.h, task.h 等 21 个文件
│ │ └── portable/
│ │ ├── ARM_CM0/ ← port.c, portasm.c, portmacro.h
│ │ └── heap_4.c
第二步:创建 FreeRTOSConfig.h
在 你的工程/empty/ 目录下新建一个文件,取名 FreeRTOSConfig.h,把下面的内容复制进去:
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*========== 基础配置 ==========*/
//开启抢占式调度。
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
// 这个是时钟频率 32MHz
#define configCPU_CLOCK_HZ ( ( unsigned long ) 32000000 )
// 这个是时间片切换的时间1000hz 为1ms
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
//可用优先级数量 = 5,即 0 ~ 4。 0 固定给空闲任务(IDLE),
//1~4 给用户任务。数字越大优先级越高,
#define configMAX_PRIORITIES ( 5 )
// 最小栈大小 = 128 words(512 字节)
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
// 堆大小 = 8KB
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 8 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 1
// vTaskDelay() 的最大延时 32位
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
//开启互斥量(Mutex)
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 1
//开启计数信号量
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_QUEUE_SETS 0
#define configUSE_TASK_NOTIFICATIONS 1
//时间片轮转开关。
#define configUSE_TIME_SLICING 1
/*========== 内存管理 ==========*/
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configAPPLICATION_ALLOCATED_HEAP 0
/*========== 定时器 ==========*/
//软件定时器功能的开关
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
/*========== 协程(M0+不支持,禁用) ==========*/
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/*========== 断言 ==========*/
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
/*========== API 函数裁剪(1=包含该函数,0=排除以节省 Flash) ==========*/
#define INCLUDE_vTaskPrioritySet 1 // vTaskPrioritySet() — 运行时修改任务优先级
#define INCLUDE_uxTaskPriorityGet 1 // uxTaskPriorityGet() — 查询任务优先级
#define INCLUDE_vTaskDelete 1 // vTaskDelete() — 删除任务
#define INCLUDE_vTaskSuspend 1 // vTaskSuspend() / vTaskResume() — 挂起/恢复任务
#define INCLUDE_xTaskDelayUntil 1 // xTaskDelayUntil() — 固定频率延迟,精确周期执行
#define INCLUDE_vTaskDelay 1 // vTaskDelay() — 相对延迟,阻塞指定 tick 数
#define INCLUDE_xTaskGetSchedulerState 1 // xTaskGetSchedulerState() — 查询调度器状态
#define INCLUDE_xSemaphoreGetMutexHolder 1 // xSemaphoreGetMutexHolder() — 查询持有互斥量的任务
#define INCLUDE_xTaskGetCurrentTaskHandle 1 // xTaskGetCurrentTaskHandle() — 获取当前任务句柄
#define INCLUDE_eTaskGetState 1 // eTaskGetState() — 查询任务状态(调试用)
#define INCLUDE_xTimerPendFunctionCall 1 // xTimerPendFunctionCall() — 在定时器任务中执行函数
/*========== 中断优先级 ==========*/
/* Cortex-M0+ 只有 4 位优先级(4 级),全部给 FreeRTOS 管理 */
// /优先级 0(最高)的中断里不要调 FreeRTOS AP
// (如 xQueueSendFromISR),会死锁。优先级 1/2/3 的中断可以调
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 3
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << ( 8 - 2 ) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << ( 8 - 2 ) )
/*========== Tickless 模式 ==========*/
#define configUSE_TICKLESS_IDLE 0
/*========== CMSIS-RTOS V2 兼容 ==========*/
#define configENABLE_FPB 0
#define configENABLE_MPU 0
#define configENABLE_TRUSTZONE 0
#define configRUN_FREERTOS_SECURE_ONLY 1
#endif /* FREERTOS_CONFIG_H */
第三步:在 Keil 工程里添加 FreeRTOS 源文件
① 打开 Keil 工程文件
双击 你的工程/empty/keil/ 里面的 .uvprojx 文件。
② 添加 FreeRTOS 源文件组
在 Keil 左边的 Project 窗口里,右键点击工程名 → Manage Project Items。
新建一个组叫 FreeRTOS Kernel,然后添加以下文件:
点击 OK 保存。
③ 添加包含路径
点击菜单栏的 Project → Options for Target(或者点那个魔术棒图标)。
切换到 C/C++ (AC6) 选项卡,在 Include Paths 后面点 ...,添加以下路径:
路径解释:Keil 工程文件在 keil/ 里,..\ = 回到 empty/ 目录,..\..\ = 回到项目根目录。
第四步:验证代码修改 main.c(empty.c)
注意:LED的引脚初始化需要配置
把原来的 empty.c 内容替换成下面的测试代码:
#include "FreeRTOS.h"
#include "task.h"
#include "ti_msp_dl_config.h"
TaskHandle_t xLED1TaskHandle = NULL;
TaskHandle_t xLED2TaskHandle = NULL;
TaskHandle_t xLED3TaskHandle = NULL;
void vLED1Task(void *pvParameters);
void vLED2Task(void *pvParameters);
void vLED3Task(void *pvParameters);
int main(void)
{
/* 初始化硬件(不初始化 SysTick,交给 FreeRTOS) */
SYSCFG_DL_initPower();
SYSCFG_DL_GPIO_init();
SYSCFG_DL_SYSCTL_init();
/* 三个 LED 初始熄灭 */
DL_GPIO_setPins(LED_LED1_PORT, LED_LED1_PIN);
DL_GPIO_setPins(LED_LED2_PORT, LED_LED2_PIN);
DL_GPIO_setPins(LED_LED3_PORT, LED_LED3_PIN);
/* 创建 3 个 LED 任务 */
xTaskCreate(vLED1Task, "LED1", 128, NULL, 1, &xLED1TaskHandle);
xTaskCreate(vLED2Task, "LED2", 128, NULL, 1, &xLED2TaskHandle);
xTaskCreate(vLED3Task, "LED3", 128, NULL, 1, &xLED3TaskHandle);
/* 启动调度器 */
vTaskStartScheduler();
for (;;) { }
}
void vLED1Task(void *pvParameters) /* PB22: 300ms 闪烁 */
{
for (;;) {
DL_GPIO_togglePins(LED_LED1_PORT, LED_LED1_PIN);
vTaskDelay(pdMS_TO_TICKS(300));
}
}
void vLED2Task(void *pvParameters) /* PA15: 500ms 闪烁 */
{
for (;;) {
DL_GPIO_togglePins(LED_LED2_PORT, LED_LED2_PIN);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vLED3Task(void *pvParameters) /* PA7: 700ms 闪烁 */
{
for (;;) {
DL_GPIO_togglePins(LED_LED3_PORT, LED_LED3_PIN);
vTaskDelay(pdMS_TO_TICKS(700));
}
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
for (;;) { }
}
void vApplicationMallocFailedHook(void)
{
for (;;) { }
}第五步:编译
点 Keil 的 Build 按钮(或按 F7)。
如果编译报错
第六步:下载验证
编译通过后,点 Download 烧录到板子。

将FreeRTOS\portable\portasm.c里面的497到420进行替换
void SVC_Handler( void ) /* __attribute__ (( naked )) PRIVILEGED_FUNCTION */
{
__asm volatile
(
" .syntax unified \n"
" .extern vPortSVCHandler_C \n"
" \n"
" movs r0, #4 \n"
" mov r1, lr \n"
" tst r0, r1 \n"
" beq stacking_used_msp \n"
" \n"
" stacking_used_psp: \n"
" mrs r0, psp \n"
" mov r1, lr \n"
" bl vPortSVCHandler_C \n"
" mov lr, r1 \n"
" bx lr \n"
" \n"
" stacking_used_msp: \n"
" mrs r0, msp \n"
" mov r1, lr \n"
" bl vPortSVCHandler_C \n"
" mov lr, r1 \n"
" bx lr \n"
" \n"
" .align 4 \n"
);
}常见问题
Q: 按步骤做完了,但一个灯都不亮 A: 用万用表量一下 PA7、PA15、PB22 有没有电压跳变。有跳变说明任务在跑,只是没焊 LED。
Q: 灯亮但不闪 A: 你的 FreeRTOSConfig.h 可能误加了 #define configSYSTICK_CLOCK_HZ,删掉它。
Q: 我有 OLED 代码想加进来 A: 在 SYSCFG_DL_SYSCTL_init() 之后加 SYSCFG_DL_SYSTICK_init() 初始化 SysTick 让 Delay 函数工作,FreeRTOS 启动后会自动接管 SysTick。
Q: 想修改 LED 闪烁频率 A: 改 vTaskDelay(pdMS_TO_TICKS(数字)) 里的数字,单位是毫秒。
Q: 想增加自己的任务 A: 照着 vLED1Task 的格式写一个函数,在 vTaskStartScheduler() 之前用 xTaskCreate() 创建。