25、STM32项目中FreeRTOS的使用与调试指南

STM32项目中FreeRTOS的使用与调试指南

1. FreeRTOS模块与配置

在使用FreeRTOS构建STM32项目时,了解相关模块和配置是非常重要的。libopencm3函数会调用FreeRTOS,例如 sys_tick_handler() 函数在系统定时器滴答中断发生时由libopencm3调用,而对 xPortSysTickHandler() 的调用则是处理系统滴答操作的FreeRTOS函数。

1.1 动态内存模块

FreeRTOS有多种动态内存模块可供选择,常见的有以下几种:
| 模块名称 | 特点 |
| ---- | ---- |
| heap_1 | 最简单,不允许释放内存 |
| heap_2 | 允许释放内存,但不合并相邻的空闲块 |
| heap_3 | 简单包装标准的 malloc() free() 以实现线程安全 |
| heap_4 | 合并相邻的空闲块以避免碎片化,包含绝对地址放置选项 |
| heap_5 | 与heap_4类似,但能够跨越多个不相邻的内存区域 |

这些源模块可能会受到 FreeRTOSConfig.h 宏设置 configAPPLICATION_ALLOCATED_HEAP 的影响。只需在Makefile中把 rtos/heap_4.c 替换为你选择的模块即可。

1.2 必需模块

除了动态内存模块 rtos/heap_*.c 外,FreeRTOS通常还需要以下模块:
- rtos/list.c :内部列表支持
- rtos/port.c :可移植性支持
- rtos/queue.c :队列和信号量支持
- rtos/tasks.c :任务支持

根据 FreeRTOSConfig.h 文件中选择的选项,在项目目录中,你可能可以排除一两个模块以实现更小的构建。例如,如果你不使用队列、互斥锁或信号量支持,就可以避免链接 rtos/queue.c

1.3 FreeRTOSConfig.h文件

FreeRTOSConfig.h 是项目级的FreeRTOS配置文件,这个头文件包含的宏设置会影响FreeRTOS模块编译到项目中的方式,也可能影响应用程序调用的任何宏。如果该文件中的任何值发生更改,在重新构建之前应该执行 make clobber

以下是 FreeRTOSConfig.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.
 *----------------------------------------------------------*/
#define configUSE_PREEMPTION            1
#define configUSE_IDLE_HOOK             0
#define configUSE_TICK_HOOK             0
#define configCPU_CLOCK_HZ              ( ( unsigned long ) 72000000 )
#define configSYSTICK_CLOCK_HZ   ( configCPU_CLOCK_HZ / 8 )
#define configTICK_RATE_HZ       ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES     ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE    ( ( size_t ) ( 17 * 1024 ) )
#define configMAX_TASK_NAME_LEN  ( 16 )
#define configUSE_TRACE_FACILITY      0
#define configUSE_16_BIT_TICKS        0
#define configIDLE_SHOULD_YIELD       0
#define configUSE_MUTEXES             1
#define configUSE_TASK_NOTIFICATIONS  1
#define configUSE_TIME_SLICING        1
#define configUSE_RECURSIVE_MUTEXES   0
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES         0
#define configMAX_CO_ROUTINE_PRIORITIES ( 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             0
#define INCLUDE_vTaskCleanUpResources   0
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil         1
#define INCLUDE_vTaskDelay              1

其中, configCPU_CLOCK_HZ configSYSTICK_CLOCK_HZ 是一对重要的宏设置。对于使用72 MHz时钟的libopencm3,通常应将 configSYSTICK_CLOCK_HZ 配置如下:

#define configSYSTICK_CLOCK_HZ ( configCPU_CLOCK_HZ / 8 )

如果配置错误,使用 vTaskDelay() 或其他与时间相关的函数的程序将出现错误。你可以通过运行 stm32f103c8t6/rtos/blinky2 中的演示来检查。配置不正确时,闪烁将不是半秒。

configTOTAL_HEAP_SIZE 宏也很重要。如果遇到错误 section '.bss' will not fit in region 'ram' ,可以通过减小堆大小直到构建成功。高级用户可以查看生成的内存映射,以确定可以重新增加堆的大小。

2. 用户库与常见错误
2.1 用户库

通常会将常用的例程(如USB或UART驱动程序)放在一个库中,以便重用。默认情况下,所有程序都会包含 ~/stm32f103c8t6/rtos/libwwg/include 中的头文件,并链接到库目录 ~/stm32f103c8t6/rtos/libwwg ,与 libwwg.a 进行链接。

但需要注意的是,这些源模块是根据 ~/stm32f103c8t6/rtos/libwwg/src/rtos/FreeRTOSConfig.h 头文件编译的,这可能与项目级的 FreeRTOSConfig.h 文件不同。如果配置有实质性差异,最好的方法是将所需的源文件复制到项目目录,并将它们添加到Makefile( SRCFILES )中,以确保子例程使用与应用程序其余部分相同的 FreeRTOSConfig.h 进行编译。

2.2 新手常见错误

在开发过程中,新手常犯的一个错误是修改头文件中的结构体,而受影响的模块没有重新编译。当结构体被更改时,成员的偏移量会改变,之前编译的模块将继续使用旧的成员偏移量。

为避免这种情况,如果更改了结构体(或C++中的类),建议先执行 make clobber ,以便从头开始重新编译所有内容。对于小项目,这样可以确保所有模块都使用相同的头文件进行编译。

3. GDB调试STM32项目

在STM32项目开发中,不可避免地会遇到需要调试的情况。GDB(Gnu Debugger)是一个非常强大的调试工具,结合ST-Link V2 USB编程器,可以方便地对STM32进行调试。

3.1 启动GDB服务器

要使用GDB调试STM32项目,首先需要启动GDB服务器。打开一个新的终端会话,运行 st-util 命令。不同情况下的运行结果如下:
- 如果未将编程器插入USB端口,运行 st-util 会出现如下提示:

$ st-util
st-util 1.3.1-4-g9d08810
2018-02-08T21:09:22 WARN src/usb.c: Couldn't find any ST-Link/V2 devices

此时,需要检查ST-Link V2编程器是否已连接并插入。
- 如果编程器已插入,但未检测到连接的STM32设备,运行结果可能如下:

$ st-util
st-util 1.3.1-4-g9d08810
2018-02-08T21:10:52 INFO src/usb.c: -- exit_dfu_mode
2018-02-08T21:10:52 INFO src/common.c: Loading device parameters....
2018-02-08T21:10:52 WARN src/common.c: unknown chip id! 0xe0042000

如果设备已连接,应立即拔下编程器以避免损坏,并重新检查接线。为避免接线错误,建议制作专用的定制电缆。
- 如果一切正常,运行结果如下:

$ st-util
st-util 1.3.1-4-g9d08810
2018-02-08T21:07:18 INFO src/usb.c: -- exit_dfu_mode
2018-02-08T21:07:18 INFO src/common.c: Loading device parameters....
2018-02-08T21:07:18 INFO src/common.c: Device connected is: F1 Medium-density device, id 0x20036410
2018-02-08T21:07:18 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x20000 bytes (128 KiB) in pages of 1024 bytes
2018-02-08T21:07:18 INFO src/gdbserver/gdb-server.c: Chip ID is 00000410, Core ID is  1ba01477.
2018-02-08T21:07:18 INFO src/gdbserver/gdb-server.c: Listening at *:4242..

从输出可以看出,已连接到F1设备(STM32F103),检测到20K字节的静态RAM,并且服务器正在监听端口4242。要终止服务器,按 ^C (Control-C)。

3.2 远程GDB调试步骤

以下是使用GDB进行远程调试的具体步骤:
1. 进入项目目录,例如RTC项目:

$ cd ~/stm32f103c8t6/rtos/rtc
  1. 启动GDB,由于 arm-none-eabi-gdb 命令较长,可设置别名:
$ alias g='arm-none-eabi-gdb'
$ g
  1. 可选步骤:如果需要进行源级符号和调试,加载符号文件:
(gdb) file main.elf
Reading symbols from main.elf...done.
  1. 连接到GDB服务器:
(gdb) target extended-remote :4242
Remote debugging using :4242
0x08003060 in ?? ()

这里省略IP地址表示通过本地回环 127.0.0.1:4242 连接。
5. 将程序加载到闪存中:

(gdb) load main.elf
Loading section .text, size 0x30e8 lma 0x8000000
Loading section .data, size 0x858 lma 0x80030e8
Start address 0x8002550, load size 14656
Transfer rate: 8 KB/sec, 7328 bytes/write.
  1. 设置断点:
(gdb) b main
Breakpoint 1 at 0x800046c: file main.c, line 252.

如果不设置断点,程序启动后会直接运行。设置断点可以在 main() 函数的第一条语句处暂停程序。
7. 启动程序:

(gdb) c
Continuing.
Breakpoint 1, main () at main.c:252
252    main(void) {
  1. 单步执行程序:
    使用 n (next)命令单步执行语句:
(gdb) n
254     rcc_clock_setup_in_hse_8mhz_out_72mhz(); // Use this for "blue pill"
(gdb) n
256     rcc_periph_clock_enable(RCC_GPIOC);
(gdb) n
257     gpio_set_mode(GPIOC,GPIO_MODE_OUTPUT_50_MHZ,
                      GPIO_CNF_OUTPUT_PUSHPULL,GPIO13);

如果要进入函数内部调试,使用 s (step)命令。
9. 继续运行程序:

(gdb) c
Continuing.

要中断程序并重新获得控制权,按 ^C (Control-C)。
10. 查看调用栈:

(gdb) bt
#0  0x08000842 in xPortPendSVHandler () at rtos/port.c:403
#1  <signal handler called>
#2  0x08000798 in prvPortStartFirstTask () at rtos/port.c:270
#3  0x080008d6 in xPortStartScheduler () at rtos/port.c:350
Backtrace stopped: Cannot access memory at address 0x20005004

这可以帮助我们了解程序的调用情况。要退出GDB,输入 quit 命令。

4. 外设相关问题及解决方法

在使用STM32外设时,可能会遇到一些常见问题,下面介绍几种常见问题及解决方法。

4.1 外设GPIO输出问题

当编写使用UART外设的新程序时,需要配置GPIO输出,但可能会遇到输出不工作的情况。例如以下错误代码:

rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_USART1);
// UART TX on PA9 (GPIO_USART1_TX)
gpio_set_mode(GPIOA,
    GPIO_MODE_OUTPUT_50_MHZ,
    GPIO_CNF_OUTPUT_PUSHPULL,
    GPIO_USART1_TX);

这段代码将PA9配置为普通GPIO输出,而不是外设输出。要使外设连接到GPIO引脚,需要将其配置为替代功能输出:

// UART TX on PA9 (GPIO_USART1_TX)
gpio_set_mode(GPIOA,
    GPIO_MODE_OUTPUT_50_MHZ,
    GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,  // NOTE!!
    GPIO_USART1_TX);
4.2 替代功能配置失败问题

在为外设初始化GPIO输出时,即使使用了正确的宏(如CAN总线的 GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN ),也可能出现问题。例如以下代码:

rcc_peripheral_enable_clock(&RCC_APB1ENR,RCC_APB1ENR_CAN1EN);
rcc_periph_clock_enable(RCC_GPIOB);
gpio_set_mode(GPIOB,
    GPIO_MODE_OUTPUT_50_MHZ,
    GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN,
    GPIO_CAN_PB_TX);
gpio_set_mode(GPIOB,
    GPIO_MODE_INPUT,
    GPIO_CNF_INPUT_FLOAT,
    GPIO_CAN_PB_RX);
gpio_primary_remap(
    AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_OFF,
    AFIO_MAPR_CAN1_REMAP_PORTB); // CAN_RX=PB8, CAN_TX=PB9

这里的问题是没有启用AFIO时钟,需要添加以下代码:

rcc_periph_clock_enable(RCC_AFIO);

否则,GPIO重映射将无法正常工作。

4.3 外设时钟未启用问题

外设需要启用自己的时钟才能正常工作。例如,对于CAN总线,需要调用以下代码启用时钟:

rcc_peripheral_enable_clock(&RCC_APB1ENR,RCC_APB1ENR_CAN1EN);

综上所述,在使用FreeRTOS开发STM32项目时,正确配置相关模块和参数,掌握调试技巧,能够有效提高开发效率,减少错误的发生。同时,对于外设的使用,要注意时钟和功能配置的正确性。

STM32项目中FreeRTOS的使用与调试指南

5. 常见错误案例及解决流程

在STM32项目开发过程中,我们会遇到各种各样的错误。下面用mermaid流程图展示常见错误及解决流程,同时列举一些具体案例。

graph TD
    A[项目开发中出现问题] --> B{是编译错误吗?}
    B -- 是 --> C[检查代码语法错误]
    C --> D{问题解决了吗?}
    D -- 是 --> E[继续开发]
    D -- 否 --> F[查看编译器详细错误信息]
    F --> C
    B -- 否 --> G{是运行时错误吗?}
    G -- 是 --> H[使用GDB调试]
    H --> I{能定位错误位置吗?}
    I -- 是 --> J[分析错误原因并修复]
    J --> D
    I -- 否 --> K[添加调试信息输出]
    K --> H
    G -- 否 --> L{是外设功能异常吗?}
    L -- 是 --> M[检查外设配置和时钟]
    M --> N{问题解决了吗?}
    N -- 是 --> E
    N -- 否 --> O[查阅数据手册和参考资料]
    O --> M
    L -- 否 --> P[检查其他可能因素]
    P --> A

以下是一些具体的错误案例及解决方法:
- 编译错误 :比如拼写错误、头文件包含错误等。例如,将函数名拼写错误,编译器会提示找不到该函数。此时,需要仔细检查代码,修正拼写错误。
- 运行时错误 :程序运行崩溃或出现异常结果。可以使用GDB调试,按照前面介绍的步骤进行操作,设置断点、单步执行等,逐步定位错误位置。
- 外设功能异常 :如前面提到的UART输出不工作、CAN总线通信失败等。需要检查外设的时钟是否启用、GPIO配置是否正确等。

6. 代码优化与性能提升

在项目开发过程中,除了保证代码的正确性,还需要考虑代码的优化和性能提升。以下是一些建议:
- 内存管理 :合理选择FreeRTOS的堆管理模块,如 heap_4 可以合并相邻的空闲块以避免碎片化。同时,根据项目需求调整 configTOTAL_HEAP_SIZE ,避免内存浪费和不足。
- 减少中断处理时间 :中断处理函数应尽量简洁,避免在中断处理函数中执行耗时操作。可以将一些复杂的处理任务放到任务中执行。
- 优化算法 :对于一些关键算法,如数据处理、通信协议解析等,选择高效的算法可以提高程序的运行速度。

下面是一个简单的示例,展示如何优化代码中的循环:

// 原始代码
for (int i = 0; i < 1000; i++) {
    // 一些操作
}

// 优化后的代码
const int loop_count = 1000;
for (int i = 0; i < loop_count; i++) {
    // 一些操作
}

在优化后的代码中,将循环次数定义为常量,避免每次循环都进行常量计算,提高了代码的执行效率。

7. 项目开发的最佳实践

为了提高项目开发的效率和质量,我们可以遵循以下最佳实践:
- 模块化设计 :将项目划分为多个模块,每个模块负责特定的功能。例如,将外设驱动、任务管理、数据处理等分别封装成独立的模块。这样可以提高代码的可维护性和可复用性。
- 版本控制 :使用版本控制系统(如Git)对项目代码进行管理。可以方便地记录代码的修改历史,便于团队协作和问题回溯。
- 文档编写 :编写详细的项目文档,包括项目概述、功能说明、代码注释等。文档可以帮助其他开发者快速理解项目,也有助于自己在后续维护中回顾代码。

以下是一个简单的模块化设计示例:

// 外设驱动模块
// gpio_driver.c
#include "gpio_driver.h"

void gpio_init() {
    // GPIO初始化代码
}

// 任务管理模块
// task_manager.c
#include "task_manager.h"
#include "gpio_driver.h"

void task_init() {
    gpio_init();
    // 任务初始化代码
}

// 主程序
// main.c
#include "task_manager.h"

int main() {
    task_init();
    // 主循环代码
    while (1) {
        // 主循环操作
    }
    return 0;
}
8. 总结与展望

在STM32项目开发中,FreeRTOS为我们提供了强大的实时操作系统支持。通过正确配置FreeRTOS的相关模块和参数,掌握GDB调试技巧,解决外设使用中的常见问题,我们可以高效地开发出稳定可靠的项目。

同时,遵循代码优化、模块化设计等最佳实践,可以进一步提高项目的质量和性能。在未来的开发中,随着技术的不断发展,我们可以探索更多的功能和应用场景,如物联网、人工智能等领域在STM32上的实现。

希望本文介绍的内容能够帮助开发者更好的进行STM32项目开发,在实际应用中不断积累经验,提升自己的开发能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值