我离不开PlatformIO和VS Code,但奈何STM32CubeMX又太好用,所以我要将STM32CubeMX创建的内容导入到PlatformIO项目中,结合两者的优点。

本文将使用我最熟悉的STM32F103C8T6。

这些东西没有是从哪儿学来的,全都是实践的经验(教训)

创建一个PlatformIO项目

在PlatformIO中创建项目,板子选STM32F103C8,框架用stm32cube。

这样子可以创建一个空白的PlatformIO项目。

创建一个STM32CubeMX项目

我强烈建议在设计硬件时就把STM32CubeMX项目建出来,能方便确定硬件设计时的接线,这次我是对着数据手册设计的硬件,创建项目时设置引脚就提心吊胆的。

我的这个硬件使用了两个串口、一个USB、一个数模转换。

(但是我在之后又加了DMA、看门狗等)

创建出来的项目就当作一个临时项目,需要什么就拿什么复制到自己的项目。

配置功能

Pinout & Configuration中,能够看到有System Core(系统核心)、Analog(模拟)、Timers(计时器)、Connectivity(接口)、Computing(计算)、Middleware and Software Packs(软件包)。

如果焊了外部晶振,需要在系统核心的RCC中配置,其中 旁路时钟源是有源晶振、晶体振荡器是无源晶振。

如果需要使用USB,那么需要使用倍频器,如果需要使用倍频器,那么需要使用外部晶振。

一定要在一开始创建项目的时候把用什么选好,如果想不清楚就多选一些,后边再改就比较麻烦了。

虽然FreeRTOS不好往PlatformIO那边移动,但是要用的话还是要选一下的,STM32CubeMX会把需要改的东西都改好。

导入生成代码

在STM32CubeMX中可以点击右上角生成代码,生成的代码大概长这个样子:

├─Core
│  ├─Inc
│  └─Src
├─Drivers
│  └─ CMSIS
├─EWARM
├─Middlewares
│  ├─ST
│  │  └─STM32_USB_Device_Library
│  └─Third_Party
│      └─FreeRTOS
└─USB_DEVICE
    ├─App
    └─Target

PlatformIO的项目长这个样子:

├─.pio
│  └─build
│      └─genericSTM32F103C8
├─.vscode
├─include
├─lib
├─src
└─test

Middlewares、Third_Party里面的东西忽略,因为PlatformIO这边有这些东西。

FreeRTOS可以用PlatformIO安装FreeRTOS Kernel这个库。

接下来只需要把头文件复制到include,源文件复制到src就好啦。

PlatformIO这边比较随意,单片机的程序结构也比较轻量,也可以把一些东西按模块放到lib里面。

设计坑

  • 多引几种接口出来,很可能就会用来调试。

  • 不要自信的以为自己写软件的时候不用调试。

  • 不熟悉的东西一定要看清楚数据手册,一点不差的照抄别人的。

    • 给USB D+上接上拉电阻。

  • 把直插的元件集中设计在边缘(尤其是排针),万一焊不好又要回加热台返工,中间的排针只能拆了。

  • 复位引脚的上拉电阻不要太大,会拉不上去。(看别人都是10K,我设计了47K,但总是出问题,换成10K就没问题了)

硬坑

  • 一定要启用调试,否则ST Link在烧录完之后就连不上机器了。

    • 如果程序导致连不上编程器了,那么可以按住Reset,在编程器连接时再松开。

  • 助焊剂一定要洗干净,导电的助焊剂会造成各种离谱问题。

    • 出现了离谱的硬件问题,板子再清洗一遍。

  • 芯片留余量,但一般芯片不会坏,测试可以做两个样品,遇到离谱问题换着试。

  • 只要不是暴力开高温(像我加热台都是用化锡的最低温度)LED一般是不管烤多久都不会坏的(我烤了数10分钟都没问题),怀疑LED时先看看自己的程序是不是有问题。

  • GPIO是要启用时钟的,灯不亮,偶尔莫名其妙亮一亮看看是不是没启用GPIO时钟。

    • STM32CubeMX提供的代码如果知道在做什么那么可以改,最好不要删,尤其是你看上去是个低级错误的额外代码,比如,你看到连续四行一模一样设置GPIO时钟的代码,不要删(因为他们是ABCD,不太显眼,看上去是一样的)。

  • 如果一个芯片正常工作很烫(比如我的充电芯片),那么它长时间工作大概率会烧穿,给它加个散热器被动散热吧...

  • 有电池的话,不要焊完自信装电池,电池的电流可以很大,有短路会送走LOD甚至主控,建议先插电脑上。有问题顶多送走个STLink

软坑(代码BUG Review)

计时器数据问题

用了FreeRTOS之后,HAL_GetTick()就无法获取到值了,因为系统计时器被FreeRTOS抢了,HAL拿不到时间了,HAL_Delay()这个函数底层是循环检查Tick,所以也不能用。

串口数据接收中断问题

如果实现了HAL_UART_RxCpltCallback不会被触发,那么检查项目有没有void USARTx_IRQHandler(void)这样子的函数,没有就是可能之前图形界面里没配置相关的中断,需要手动实现这个函数和在init里使用HAL_NVIC_EnableIRQ启用中断(同时使用HAL_NVIC_SetPriority设置优先级,亲测RTOS下5的优先级是能用的)。

栈溢出的问题

我用了RTOS,并且设置了#define configCHECK_FOR_STACK_OVERFLOW 2#define configUSE_STACK_OVERFLOW_HOOK 1在堆栈溢出时触发回调打印。

经测试,检测到溢出一定会出莫名其妙的问题,但并非溢出一定检测到,程序出莫名其妙的问题可以考虑往上加加堆栈。

(像我这种在打印调试文本宏里面写了sprintf的只要程序在测试阶段跑起来了,生产环境就不会溢出了吧~)

互斥锁的问题

FreeRTOS里面有二值信号量和互斥锁,互斥锁功能高级一些,写Go习惯的我,当然就直接Mutex啦,炸了,迷惑了很久。

xSemaphoreCreateMutex()创建的互斥锁因为高级,所以ISR里面不可以使用,使用xSemaphoreCreateBinary()创建二值信号量。

修改频率的问题

机器上电的时候用的是内部晶振,需要用代码切换。

这个切换的代码一般第一次执行总是能成功的,除非没焊好。

但如果为了降低功耗需要运行时切换频率,那么需要注意。

锁相环倍频修改之前必须先让系统频率不要使用锁相环,否则会失败。

修改之后需要重新初始化串口、重新计算毫秒分频。

Printf浮点数的问题

无论是printf还是sprintf都无法使用 %f 占位符,考虑其他方法。

sprintf栈使用

虽然sprintf需要事先创建缓冲区,对人来说很难用,但它对机器来说也不是个省油的灯,在我的Debug宏中大量使用了它,它会使用大量的栈空间。

CBOR解码问题

为了减小硬件负担和带宽,我没用JSON,而是用了CBOR。

tinycbor这个库在访问套在Map里面的Map的时候需要执行进入操作,进入后就不能对外面的map操作,需要再退出,对于嵌入式硬件程序上需要让一段程序解析外面的,一段程序解析里面的不友好。

错误的操作

  1. 进入Map

  2. 拷贝一份迭代器

  3. 退出Map

  4. 继续进行外层操作

  5. 开始内层操作,使用拷贝的迭代器

经测试,最终使用的时候似乎存在未定义行为,迭代器会死循环。

正确的操作

  1. 拷贝一份没有进入Map的迭代器

  2. 继续进行外层操作

  3. 使用拷贝的迭代器进入Map

  4. 开始内层操作

很神奇。

ADC转换时间问题

如果程序测出来的电压总是比手动量出来低,而且还是一本正经的低(多次测量值一样),考虑增加ADC转换的时间,尤其是像我这样外面还加了个100K电阻还没个电容的。

串口读写错误问题

莫名奇妙板子摸一下就复位了。是不是因为接受了HAL_UART_ErrorCallback 回调,而且以为它很致命,直接在里面写了断言之类的?

实际上这个错误很容易出现。

我能想到的,最大的成功就是无愧于自己的心。