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
-
启动GDB,由于
arm-none-eabi-gdb命令较长,可设置别名:
$ alias g='arm-none-eabi-gdb'
$ g
- 可选步骤:如果需要进行源级符号和调试,加载符号文件:
(gdb) file main.elf
Reading symbols from main.elf...done.
- 连接到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.
- 设置断点:
(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) {
-
单步执行程序:
使用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项目开发,在实际应用中不断积累经验,提升自己的开发能力。
超级会员免费看
3897

被折叠的 条评论
为什么被折叠?



