STM32开发常见问题及解决策略
1. 外设时钟启用
对于UART等外设,启用时钟的调用可能很简单,例如:
rcc_periph_clock_enable(RCC_USART1);
显然,如果外设时钟被禁用(如复位后),外设将无法正常工作。
2. ISR中FreeRTOS崩溃问题
FreeRTOS对在中断服务程序(ISR)中可以调用和不能调用的函数有规则。任务可以随时调用
xQueueSend()
,但ISR必须使用
xQueueSendFromISR()
函数。中断本质上是异步的,任何非可重入的函数调用都可能有问题。违反此规则可能会导致间歇性故障。
3. 栈溢出问题
创建任务时必须预先确定栈大小,例如:
xTaskCreate(monitor_task,"monitor",350,NULL,1,NULL);
上述调用的第三个参数为任务栈分配了350个字的存储空间(每个字4字节)。
xTaskCreate()
函数从堆中分配栈空间。如果栈大小不足,会导致内存损坏,结果不可预测。
FreeRTOS提供了三种检查栈溢出的方法,由
FreeRTOSConfig.h
文件中的
configCHECK_FOR_STACK_OVERFLOW
宏定义决定:
| 宏定义值 | 检查方式 | 效率 |
| ---- | ---- | ---- |
| 0 | 不检查溢出,运行效率最高 | 最高 |
| 1 | 进行快速栈检查 | 中等 |
| 2 | 进行更彻底的栈检查,运行效率最低 | 最低 |
当宏定义为非零值时,必须提供一个在栈溢出时调用的函数:
void
vApplicationStackOverflowHook(
xTaskHandle *pxTask,
signed portCHAR *pcTaskName
) {
// 可以做一些操作,比如闪烁LED
}
4. 栈大小估计
对于调用库例程(尤其是第三方库)的函数,估计所需栈大小可能很困难。FreeRTOS提供了一个帮助函数:
#include "FreeRTOS.h"
#include "task.h"
// 返回字的数量
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t task);
该函数返回未使用的栈字数。返回值越接近零,任务栈溢出的可能性就越大。不过,该函数调用成本较高,建议仅在调试时使用。
5. 调试器无法发挥作用的情况
调试设备驱动程序时,可能会涉及中断和超时,单步执行代码可能无济于事。此时,可以收集崩溃位置或最后成功处理的事件等线索。在ISR中,可以通过激活LED等简单方式传达事件。如果有数字存储示波器(DSO)或逻辑分析仪,在多个GPIO上发出信号可以提供很多信息。在更极端的情况下,可以预留一个跟踪缓冲区,使用GDB中断STM32的执行并检查该缓冲区。
6. GPIO输出配置问题
一些问题集中在GPIO输出的使用上。例如,很多论坛帖子称SPI的硬件从机选择不起作用,但使用开漏配置和上拉电阻就可以解决。STM32支持多主模式SPI,多主总线元素必须使用上拉电阻,这样可以避免不同控制器之间的冲突。阅读STMicroelectronics数据手册时,要注意细则和脚注。
7. 外设缺陷
极少数情况下,可能会遇到外设行为不正确的问题。STM32外设是复杂的硅状态机,在某些情况或配置下可能存在缺陷。可以搜索并下载“STM32F1 Errata Sheet” PDF文件,了解可能出现的问题,通常会提供解决方法。
8. 资源获取
让STM32外设按预期工作可能会花费大量时间。获取外设信息的最佳来源是STMicroelectronics的“参考手册”RM0008,还可以搜索并下载“STM32F103x8 STM32F103xB PDF”文档,其中的表5“Medium-density STM32F103xx pin definitions”包含了重要的引脚配置信息。“Electrical Characteristics”部分对于估计功耗很有帮助。
9. libopencm3使用问题
libopencm3虽然有API维基,但开发新应用时可能会遇到一些未提供或不明显的答案。例如,不清楚何时可以在函数调用参数中组合不同值,何时必须分开提供。可以通过阅读API文档和查看源代码来解决这些问题。
例如,对于函数
void gpio_set(uint32_t gpioport,uint16_t gpios);
,可以使用C或(|)运算符在第二个参数中组合多个GPIO引用:
gpio_set(GPIOB,GPIO5|GPIO5);
但不能组合
gpioport
的值。
对于函数
bool usart_get_flag(uint32_t usart,uint32_t flag);
,如果组合标志,只会得到一个布尔结果,可能不是想要的。该函数定义如下:
bool usart_get_flag(uint32_t usart, uint32_t flag)
{
return ((USART_SR(usart) & flag) != 0);
}
10. FreeRTOS任务优先级
FreeRTOS提供多任务处理和多个优先级级别。任务优先级从0(最低优先级)到
configMAX_PRIORITIES - 1
(最高优先级),
configMAX_PRIORITIES
宏在
FreeRTOSConfig.h
文件中定义。空闲任务优先级为0,在没有其他任务准备好运行时运行。
FreeRTOS调度器会将CPU分配给处于就绪或运行状态的任务。如果有更高优先级的任务处于就绪或运行状态,低优先级任务将不会运行。这与Linux不同,在Linux中CPU会在不同优先级的进程间共享。在FreeRTOS中,低优先级任务需要高优先级任务处于挂起或阻塞状态才能获得CPU。
如果低优先级任务看起来冻结或挂起,可能需要调整优先级方案或确保任务按预期被阻塞/挂起。
11. libopencm3中的调度问题
libopencm3库独立于FreeRTOS开发,外设驱动程序等待外设事件时通常包含忙循环。例如:
void usart_wait_send_ready(uint32_t usart)
{
/* 等待数据传输到移位寄存器 */
while ((USART_SR(usart) & USART_SR_TXE) == 0);
}
该函数会消耗整个时间片,直到抢占式中断发生才会将CPU交给其他任务,效率较低且可能引入过多延迟。
为了更好地利用CPU,可以采取以下几种方法:
1. 接受现状(如果对应用程序不是关键问题)。
2. 将函数复制到自己的代码中并添加
taskYIELD()
调用。
3. 修改自己的libopencm3库副本。
4. 实现钩子功能并提交给libopencm3志愿者。
最简单的解决方法是第二种,修改后的代码如下:
void usart_wait_send_ready(uint32_t usart)
{
/* 等待数据传输到移位寄存器 */
while ((USART_SR(usart) & USART_SR_TXE) == 0)
taskYIELD(); // 使FreeRTOS友好
}
12. 总结与拓展
之前主要关注了STM32F103C8T6型号。如果想迎接更高级的挑战,可以考虑STM32F407系列设备,如Discovery开发板。STM32F4在SRAM、闪存和外设方面提供了更多功能,还包含硬件浮点运算,对某些应用程序的性能至关重要。
附录:练习题答案
第4章
-
蓝色药丸PCB上的内置LED使用哪个GPIO端口?请指定libopencm3宏名称。
答案:PORTC -
蓝色药丸PCB上的内置LED使用哪个GPIO引脚?请指定libopencm3宏名称。
答案:GPIO13 -
蓝色药丸PCB上的内置LED需要什么电平才能点亮?
答案:逻辑低电平(或零伏) -
在非多任务环境中,编程延迟中选择的循环次数受哪两个因素影响?
答案:- CPU时钟速率
- 指令执行时间
-
为什么在多任务环境中不使用编程延迟?
答案:因为系统中其他任务的时间安排会影响编程延迟的 elapsed 时间。 -
影响指令时序的三个因素是什么?
答案:- 所选平台
- CPU时钟速率
- 执行上下文(在闪存或SRAM中运行代码)
-
输入GPIO端口的三种模式是什么?
答案:- 模拟
- 数字,浮空
- 数字,上拉和下拉
-
弱上拉和下拉电阻是否参与模拟输入?
答案:否 -
输入端口的施密特触发器何时启用?
答案:GPIO或外设数字输入 -
弱上拉和下拉电阻是否参与输出GPIO端口?
答案:否,它们仅适用于输入。 -
为USART TX(发送)输出配置推挽操作时,应使用哪个专用宏?
答案:GPIO_CNF_OUTPUT_ALTFN_PUSHPULL -
配置引脚用于LED时,哪个GPIO模式宏更适合低电磁干扰(EMI)?
答案:GPIO_MODE_OUTPUT_2_MHZ(像GPIO_MODE_OUTPUT_10_MHZ这样的高速选择会消耗更多电流并产生额外的EMI)
第5章
-
blinky2中有多少个任务在运行?
答案:1 -
blinky2中有多少个控制线程在运行?
答案:两个线程:主线程和任务1 -
如果将
configCPU_CLOCK_HZ的值配置为36000000,blinky2的闪烁速率会发生什么变化?
答案:闪烁速率会加倍,因为FreeRTOS调度器期望CPU速度减半。 -
任务1的栈从哪里来?
答案:任务1的栈从堆中分配。 -
任务1() 何时开始?
答案:当调用vTaskStartScheduler()函数时 -
为什么需要消息队列?
答案:为了在不同的控制线程之间安全通信 -
即使使用执行延迟循环,为什么它似乎能以近50%的占空比工作?
答案:因为只有一个任务在执行,时序保持相当一致。 -
估计PC13上的LED点亮多长时间有多困难?为什么?
答案:由于指令时序、闪存预取等原因,很难估计。 -
使用示波器测量PC13的开和关时间(或计算每秒闪烁次数并取倒数)。LED点亮了多少毫秒?
答案:84 ms -
如果向该项目添加另一个消耗大部分CPU的任务,闪烁速率会受到怎样的影响?
答案:闪烁速率会显著减慢。
STM32开发常见问题及解决策略
13. 总结策略流程
为了更清晰地展示解决STM32开发中常见问题的策略,下面给出一个mermaid格式的流程图:
graph LR
A[遇到问题] --> B{问题类型}
B -->|外设时钟| C(启用外设时钟,如 rcc_periph_clock_enable(RCC_USART1))
B -->|ISR崩溃| D(使用xQueueSendFromISR代替xQueueSend)
B -->|栈溢出| E{选择检查方式}
E -->|0| F(不检查溢出)
E -->|1| G(快速栈检查)
E -->|2| H(彻底栈检查,提供溢出钩子函数)
B -->|栈大小估计| I(使用uxTaskGetStackHighWaterMark函数)
B -->|调试器无效| J(收集线索,使用LED或跟踪缓冲区)
B -->|GPIO输出| K(使用开漏配置和上拉电阻)
B -->|外设缺陷| L(下载Errata Sheet查找解决方法)
B -->|资源获取| M(参考RM0008和相关文档)
B -->|libopencm3使用| N(阅读API文档和查看源代码)
B -->|任务优先级| O(调整优先级方案或确保任务阻塞/挂起)
B -->|调度问题| P{选择解决方法}
P -->|1| Q(接受现状)
P -->|2| R(复制函数并添加taskYIELD())
P -->|3| S(修改libopencm3库副本)
P -->|4| T(实现钩子功能并提交)
14. 关键知识点表格汇总
| 问题类型 | 解决方法 | 代码示例 |
|---|---|---|
| 外设时钟启用 |
使用
rcc_periph_clock_enable
函数
|
rcc_periph_clock_enable(RCC_USART1);
|
| ISR中FreeRTOS崩溃 |
使用
xQueueSendFromISR
函数
| |
| 栈溢出检查 |
根据
configCHECK_FOR_STACK_OVERFLOW
宏选择检查方式
| |
| 栈大小估计 |
使用
uxTaskGetStackHighWaterMark
函数
|
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t task);
|
| 调试器无效 | 收集线索,使用LED或跟踪缓冲区 | |
| GPIO输出配置 | 使用开漏配置和上拉电阻 | |
| 外设缺陷 | 下载“STM32F1 Errata Sheet” PDF文件 | |
| libopencm3使用 | 阅读API文档和查看源代码 | |
| FreeRTOS任务优先级 | 调整优先级方案或确保任务阻塞/挂起 | |
| libopencm3调度问题 |
复制函数并添加
taskYIELD()
等方法
|
void usart_wait_send_ready(uint32_t usart) { while ((USART_SR(usart) & USART_SR_TXE) == 0) taskYIELD(); }
|
15. 代码使用注意事项列表
- 外设时钟启用 :确保在使用外设前启用其时钟,否则外设将无法正常工作。
- ISR中FreeRTOS函数调用 :严格遵循FreeRTOS规则,在ISR中使用特定的函数,避免崩溃。
- 栈溢出检查 :根据实际需求选择合适的栈溢出检查方式,并提供相应的钩子函数。
-
栈大小估计
:使用
uxTaskGetStackHighWaterMark函数时,注意其调用成本较高,仅在调试时使用。 - libopencm3函数参数组合 :仔细阅读API文档,确定函数参数是否可以组合使用,必要时查看源代码。
- 任务优先级设置 :理解FreeRTOS任务优先级机制,根据任务的重要性合理设置优先级,避免低优先级任务被“冻结”。
-
libopencm3调度优化
:根据应用需求选择合适的调度优化方法,如添加
taskYIELD()调用提高CPU利用率。
16. 综合案例分析
假设我们正在开发一个基于STM32的项目,遇到了以下情况:
- 串口通信无法正常工作。
- 任务运行一段时间后出现异常。
针对这些问题,我们可以按照以下步骤进行排查和解决:
1.
检查外设时钟
:确认串口外设的时钟是否已启用,使用如下代码:
rcc_periph_clock_enable(RCC_USART1);
- 检查ISR函数调用 :查看ISR中是否正确使用了FreeRTOS函数,确保没有违反规则。
-
检查栈溢出
:设置
configCHECK_FOR_STACK_OVERFLOW宏为合适的值,并提供栈溢出钩子函数,例如:
#define configCHECK_FOR_STACK_OVERFLOW 1
void vApplicationStackOverflowHook(
xTaskHandle *pxTask,
signed portCHAR *pcTaskName
) {
// 闪烁LED表示栈溢出
// 相关代码实现
}
-
估计栈大小
:使用
uxTaskGetStackHighWaterMark函数检查任务的栈使用情况,判断是否存在栈溢出风险。
UBaseType_t unused_stack = uxTaskGetStackHighWaterMark(task_handle);
- 检查GPIO配置 :如果涉及GPIO输出问题,检查是否使用了正确的配置,如开漏配置和上拉电阻。
- 查看外设缺陷文档 :下载“STM32F1 Errata Sheet” PDF文件,查看是否存在已知的外设问题及解决方法。
-
优化调度
:对于libopencm3中的调度问题,考虑复制相关函数并添加
taskYIELD()调用,提高CPU利用率。
通过以上步骤,我们可以逐步排查并解决项目中遇到的问题,确保系统的稳定运行。
17. 未来开发建议
- 在开发前,充分了解STM32外设的特性和使用方法,阅读相关文档和数据手册,避免因基础知识不足导致的问题。
- 编写代码时,遵循良好的编程规范,合理设置任务优先级和栈大小,避免潜在的问题。
- 在调试过程中,善于利用各种工具和方法,如LED指示灯、DSO、逻辑分析仪等,快速定位问题。
- 对于libopencm3库,积极参与社区讨论,提交自己的改进和问题反馈,共同完善库的功能。
总之,STM32开发过程中会遇到各种挑战,但通过掌握相关知识和解决策略,我们可以有效地应对这些问题,开发出稳定、高效的应用程序。希望以上内容对大家在STM32开发中有所帮助。
超级会员免费看
1792

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



