每次创建新项目的时候我都觉得很麻烦,哪怕所有的库都是写好的还是很麻烦。尤其是 EUBF 字库,强绑定一个线程用来执行存储介质状态机。不过不过不使用 EUBF 的话我就得把我需要的汉字手动提取字模了……更麻烦了。
说到这点,我一开始时处理 SD 卡挂载是想写一个阻塞函数在 main 里死等直到超时,但却发现 HAL_Delay 不知为何失效了。
经过艰苦排查最后发现由于我在 FATFS 组件里开启了文件重入(
_FS_REENTRANT),导致当我尝试挂载文件系统时,f_mont在syscall.c里调用了osMutexNew,这个函数最终会调用xQueueGenericReset,而它会进入临界区。我们说过进入临界区是有一个计数器来处理嵌套的,即
port.c里的uxCriticalNesting,而这个变量初始被赋值为 0xaaaaaaaa。因此虽然程序正确的调用了退出临界区的函数,但这个计数器没有被归零,因此系统此时依然处于中断屏蔽状态里。
最终,当代码执行到
osKernelStart时,系统会调用xPortStartScheduler来执行第一个任务,并在这里将计数器和中断屏蔽寄存器清零。为什么 FreeRTOS 要这么做?
当程序第一次调用 rtos 相关函数时,系统便认为此时正在搭建系统运行环境。如果此时突如其来一个硬件中断,且该中断里调用了 rtos 的 API 函数的话,由于 rtos 系统仍未初始化完毕,任何 API 都可能指向一个错误的指针或内存空间。为了防止这种情况,freertos 会保持中断屏蔽状态直到开启调度器。
我发现将 TIM6 定时器中断优先级从 15 调整到 0 时这个问题就会消失,因此我怀疑是 freertos 发力了,因为此时没有别的中断会抢占 TIM6 的中断。经过多次调整优先级后我确定了这个想法,因为只要优先级高于 5 就能正常延时。
之后就是询问 AI freertos 会不会在初始化前就开启中断屏蔽,AI 说不会。我又说我同时在使用 FATFS 和 SDIO_SD,AI 说 FF 的文件重入会根据是否使用 rtos 创建互斥量,我去看了一下还真是如此,随后沿着调用栈不断深入,我发现创建互斥量会进入临界状态。
但这依然没有解决我的疑惑,因为这些代码是 CubeMX 生成的,肯定不会出现忘记退出这种操作。随后我又在临界区相关的两个函数(vPortRaiseBASEPRI和vPortSetBASEPRI)里设置断点,发现它们压根就没被调用过。
这时 AI 建议我去查看系统寄存器,尤其是 xPSR 和 BASEPRI ,找到这俩在哪又费了我一番功夫。查看寄存器过后终于能确定系统处于中断屏蔽的状态里。于是我又问为什么正常退出了还是会屏蔽中断,AI 给了我上面的答复。
那为什么我之前设置断点没有触发呢?因为这两个函数被设置为强制内联函数1,也就是说在编译时就把函数调用替换成函数里面的内容以减少调用函数的时间。这样做会让相关的断点失效。当我将断点设置在它们下方时,就能发现问题了:


此时查看调用堆栈可知,挂载SD用的f_mont会调用一系列函数来创建互斥量,最终调用vPortEnterCritical进入临界区。
那我还能说什么呢,这里面随便一个难题就不是我能解决的了。我给 AI 磕一个呗。2
