26、STM32开发常见问题及解决策略

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章
  1. 蓝色药丸PCB上的内置LED使用哪个GPIO端口?请指定libopencm3宏名称。
    答案:PORTC
  2. 蓝色药丸PCB上的内置LED使用哪个GPIO引脚?请指定libopencm3宏名称。
    答案:GPIO13
  3. 蓝色药丸PCB上的内置LED需要什么电平才能点亮?
    答案:逻辑低电平(或零伏)
  4. 在非多任务环境中,编程延迟中选择的循环次数受哪两个因素影响?
    答案:
    • CPU时钟速率
    • 指令执行时间
  5. 为什么在多任务环境中不使用编程延迟?
    答案:因为系统中其他任务的时间安排会影响编程延迟的 elapsed 时间。
  6. 影响指令时序的三个因素是什么?
    答案:
    • 所选平台
    • CPU时钟速率
    • 执行上下文(在闪存或SRAM中运行代码)
  7. 输入GPIO端口的三种模式是什么?
    答案:
    • 模拟
    • 数字,浮空
    • 数字,上拉和下拉
  8. 弱上拉和下拉电阻是否参与模拟输入?
    答案:否
  9. 输入端口的施密特触发器何时启用?
    答案:GPIO或外设数字输入
  10. 弱上拉和下拉电阻是否参与输出GPIO端口?
    答案:否,它们仅适用于输入。
  11. 为USART TX(发送)输出配置推挽操作时,应使用哪个专用宏?
    答案:GPIO_CNF_OUTPUT_ALTFN_PUSHPULL
  12. 配置引脚用于LED时,哪个GPIO模式宏更适合低电磁干扰(EMI)?
    答案:GPIO_MODE_OUTPUT_2_MHZ(像GPIO_MODE_OUTPUT_10_MHZ这样的高速选择会消耗更多电流并产生额外的EMI)
第5章
  1. blinky2中有多少个任务在运行?
    答案:1
  2. blinky2中有多少个控制线程在运行?
    答案:两个线程:主线程和任务1
  3. 如果将 configCPU_CLOCK_HZ 的值配置为36000000,blinky2的闪烁速率会发生什么变化?
    答案:闪烁速率会加倍,因为FreeRTOS调度器期望CPU速度减半。
  4. 任务1的栈从哪里来?
    答案:任务1的栈从堆中分配。
  5. 任务1() 何时开始?
    答案:当调用 vTaskStartScheduler() 函数时
  6. 为什么需要消息队列?
    答案:为了在不同的控制线程之间安全通信
  7. 即使使用执行延迟循环,为什么它似乎能以近50%的占空比工作?
    答案:因为只有一个任务在执行,时序保持相当一致。
  8. 估计PC13上的LED点亮多长时间有多困难?为什么?
    答案:由于指令时序、闪存预取等原因,很难估计。
  9. 使用示波器测量PC13的开和关时间(或计算每秒闪烁次数并取倒数)。LED点亮了多少毫秒?
    答案:84 ms
  10. 如果向该项目添加另一个消耗大部分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);
  1. 检查ISR函数调用 :查看ISR中是否正确使用了FreeRTOS函数,确保没有违反规则。
  2. 检查栈溢出 :设置 configCHECK_FOR_STACK_OVERFLOW 宏为合适的值,并提供栈溢出钩子函数,例如:
#define configCHECK_FOR_STACK_OVERFLOW 1

void vApplicationStackOverflowHook(
  xTaskHandle *pxTask,
  signed portCHAR *pcTaskName
) {
    // 闪烁LED表示栈溢出
    // 相关代码实现
}
  1. 估计栈大小 :使用 uxTaskGetStackHighWaterMark 函数检查任务的栈使用情况,判断是否存在栈溢出风险。
UBaseType_t unused_stack = uxTaskGetStackHighWaterMark(task_handle);
  1. 检查GPIO配置 :如果涉及GPIO输出问题,检查是否使用了正确的配置,如开漏配置和上拉电阻。
  2. 查看外设缺陷文档 :下载“STM32F1 Errata Sheet” PDF文件,查看是否存在已知的外设问题及解决方法。
  3. 优化调度 :对于libopencm3中的调度问题,考虑复制相关函数并添加 taskYIELD() 调用,提高CPU利用率。

通过以上步骤,我们可以逐步排查并解决项目中遇到的问题,确保系统的稳定运行。

17. 未来开发建议
  • 在开发前,充分了解STM32外设的特性和使用方法,阅读相关文档和数据手册,避免因基础知识不足导致的问题。
  • 编写代码时,遵循良好的编程规范,合理设置任务优先级和栈大小,避免潜在的问题。
  • 在调试过程中,善于利用各种工具和方法,如LED指示灯、DSO、逻辑分析仪等,快速定位问题。
  • 对于libopencm3库,积极参与社区讨论,提交自己的改进和问题反馈,共同完善库的功能。

总之,STM32开发过程中会遇到各种挑战,但通过掌握相关知识和解决策略,我们可以有效地应对这些问题,开发出稳定、高效的应用程序。希望以上内容对大家在STM32开发中有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值