FreeRTOS 移植保姆级教程 — MSPM0G3507 + Keil


准备工作

你需要的东西

  1. Keil MDK(已装好)

  2. TI MSPM0 SDK(已装好)

  3. FreeRTOS 源码包(去 GitHub 搜 FreeRTOS-Kernel 下载 V11.3.0)

  4. 你的 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,然后添加以下文件:

组名

添加的文件

FreeRTOS Kernel

FreeRTOS\src\tasks.c

FreeRTOS\src\queue.c

FreeRTOS\src\list.c

FreeRTOS\src\timers.c

FreeRTOS\src\event_groups.c

FreeRTOS\src\stream_buffer.c

FreeRTOS\src\croutine.c

FreeRTOS\portable\ARM_CM0\port.c

FreeRTOS\portable\ARM_CM0\portasm.c

FreeRTOS\portable\heap_4.c

点击 OK 保存。

③ 添加包含路径

点击菜单栏的 Project → Options for Target(或者点那个魔术棒图标)。

切换到 C/C++ (AC6) 选项卡,在 Include Paths 后面点 ...,添加以下路径:

路径

说明

..\..\source\third_party\CMSIS\Core\Include

CMSIS 头文件

..\..\source

TI SDK 根目录

..\System

Delay.h

..\..\empty

FreeRTOSConfig.h 的位置

..\FreeRTOS\include

FreeRTOS 内核头文件

..\FreeRTOS\portable\ARM_CM0

portmacro.h 的位置

..\DriveLib

驱动源文件

..\Hardare

..\OLED

OLED 驱动

路径解释: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)。

如果编译报错

错误信息

怎么办

'FreeRTOS.h' file not found

检查包含路径有没有加 ..\FreeRTOS\include

'portmacro.h' file not found

检查包含路径有没有加 ..\FreeRTOS\portable\ARM_CM0

'ti_msp_dl_config.h' file not found

检查包含路径有没有加 ..\..\empty

'ti/driverlib/xxx.h' file not found

检查包含路径有没有加 ..\..\source

use of undeclared identifier 'LED_PORT'

你的 SysConfig 改成了 LED_LED1_PORT,代码里用 LED_LED1_PORT


第六步:下载验证

编译通过后,点 Download 烧录到板子。

现象

说明

LED1(PB22) 以 300ms 闪烁

移植成功 ✅

LED2(PA15) 以 500ms 闪烁

移植成功 ✅

LED3(PA7) 以 700ms 闪烁

移植成功 ✅

三个 LED 都不亮

检查板子上这些引脚有没有焊 LED

只有一个 LED 亮但不闪

SysTick 时钟源配置错误,检查 FreeRTOSConfig.h 有没有误加 configSYSTICK_CLOCK_HZ


将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() 创建。