【嵌入式】CLion开发STM32工程
CLion嵌入式开发的环境搭建
OS:Windows 11
CLion:2023.3.3→2024.2→2025.2
STM32CubeMX:6.9.0
引言
做嵌入式开发,一定对Keil、IAR这类远古IDE不陌生,虽编译、调试功能很强但画风有些远古了。好一点的解决方案是像IAR、Keil、Quartus ii等联调VS Code,即在VS Code编写(调试)代码,在IAR或Keil编译烧录。以VS Code强大而丰富的插件功能和简约优美的UI,可以使开发体验得到大幅提升。更进一步的是,编写与编译烧录一体化,配置配置VS Code也能做到。
而这里则提供另一款嵌入式IDE——CLion(缘于稚晖君大佬的博客),使用过JetBrains产品的一定对它印象深刻,比如Pycharm,界面美观、代码补全灵活等,各类功能既完善且强大。由于CLion不像IAR、Keil那样有完善的嵌入式工具链,所以需要手动配置工具链,进行很多操作。如果某个环节没有如教程那样复现,那将会是顶级的折磨,所以要做好心理准备和取舍,这里就先祝大家一步到位。
2024.10.13
并且用户名必须是英文等合法字符,不能含有中文、空格啥的,不然CLion连自己的构建工具都找不到!!如果你出现CMake运行错误什么什么的,那应该是用户名的问题
更改用户名可参考链接更改电脑本地用户名,将中文用户名给为英文名称
一、工具准备 ૮₍ ˃ ⤙ ˂ ₎ა
1,工具链
本着版本越新越好的原则(虽然往往会出现各种令人不快的兼容性问题),这里的版本都尽可能地为当下最新版
关于CLion的安装,这里不赘述了,现在CLion也有免费版的,可以到官网直接下载。需要注意CLion的安装路径上不能出现中文(除非电脑的编码系统已经改成UTF-8了),电脑的用户名也不能含有中文,这个可以到C盘的用户里去查看
也可以按下Win+R,输入netplwiz,进入账户
可以看到这里的用户名是英文,一般来说【组】这里是Administors;Users,我这里是新创建的用户。这里的操作务必需要谨慎!!如果想要更改用户名,可自行查阅相关博客,切莫莽撞!
2024.10.13
用户名必须是英文等合法字符,不能含有中文、空格啥的,不然CLion连自己的构建工具都找不到!!如果你出现CMake运行错误什么什么的,那应该是用户名的问题
更改用户名可参考链接更改电脑本地用户名,将中文用户名给为英文名称
CLion25.2相比于CLion24.2,添加了新手引导步骤,在我们随便打开一个cmake工程时会弹出(这里先以它为例,也可以自己随便创建一个C/C++工程,后面会讲讲如何从头使用CLion构建一个STM32工程)。为此,我们需要配置工具链,工具链是干什么的呢?
工具链
平时开发STM32工程,会写一些.h/.c文件,但这些本质上都是文本文件,与.txt文件没什么区别。想要把它变成可执行程序(在操作系统上能执行的程序,比如.exe、.elf文件等)或者二进制文件(可以烧录在ROM上,加载到内存中,供CPU读取执行),那么需要一个工具或者一些工具来完成这个操作,这些工具(以及它们自带的库等)共同构成了工具链,比如常用的MinGW工具链(包含的是GCC编译器,MinGW是其在Windows下的项目名,这其中的渊源可自查),其中包含了gcc、ar、objump等工具,它们就像链条一样,前一个工具处理过后的产物交给下一个工具继续处理。
交叉编译工具链
理解了何为工具链之后,接下来我们考虑一个事情,一个平台比如Windows,它编译出的可执行程序或者二进制文件应该是什么架构的呢?对于常见的64位机x64,它编译出的文件也是64位的,那么是不是很自然,很合理。
但我们要开发的是STM32,是32位机,并且是ARM32架构的,那么它自然无法识别64位机编译出的二进制文件。那么怎么办呢?显而易见的是,在同样是ARM32的机器上编译出二进制文件,但是一般来说ARM架构并不以性能见强,我们所常见的MCU芯片,比如STM32F407,单核ARM32架构并且主频只有168MHz,与现代电脑上GHz的主频没有丝毫可比性。
如果在这样的电脑上编译二进制文件将是一种可怕的灾难,因此交叉编译工具链的出现合乎其理且很必要。这其中会涉及主机(Host)和目标机(Target)的概念,主机自然是大家日常使用的x68_x64的电脑,目标机也就是我们的目标平台,如果目标平台是像STM32这种ARM架构的,那么就是ARM交叉编译工具链;如果目标平台是安卓手机,那么就是安卓交叉编译工具链
2,安装工具链
工具链不是唯一的,在嵌入式开发里,我们常用的是GCC工具链,它是开源免费的,此外常用的还有Keil和IAR自己的工具链,它们会被集成到各自的IDE中,以图形化的形式显现,所以平时我们不大能感受到。
如果范围再扩大一点,看向上位机开发(可以理解为编译出电脑上能直接运行的程序,比如.exe文件),那么除了GCC工具链外,还有LLVM工具链和MSVC工具链。与前两者是支持广泛平台的开源工具链不同,MSVC工具链是微软自己开发的。
现在回到我们的STM32开发上,我们需要的是一个主机为Windows,目标机为ARM32的gcc交叉编译工具链,可以直接在浏览器中搜索到
进去后并不是最新的工具链,点击箭头所示处,可以看到目前最新的版本是14.2
好吧,隔了几分钟版本就变成了14.3,挺巧的。之所以需要这么在意版本,是因为不同版本的编译器所支持的C++标准不同,比如gcc14支持C++20,而以前的版本如gcc13最高支持C++17。
gcc14是个很关键的版本,在gcc 14之前的编译器并不支持C++20的module特性,而gcc14也只是勉强支持这个特性,gcc15对C++20的支持要更好一些,不过现在这个版本的编译器只在“上位机开发”的gcc上出现过,我们所要用的ARM交叉编译器gcc并没有gcc15,只有gcc 14.3
回到正题,可以看到红框上是Windows(mingw-w64-i686) hosted cross toolchains
Windows(mingw-w64-i686)hosted表明了它的主机(hosted)是Windows,而i686表明了它是32位程序,有时候也用x86表示它是32位程序。x64、x86_x64、amd64则表示它是64位程序,这些64位程序都是兼容32位程序的(不兼容的已经消逝在历史长河中了,不然也不会出现amd64这个称呼了。不过ARM不在此列)。cross toolchains表明它是交叉(cross)工具链(toolchains)
红框内的上方是AArch32 bare-metal target(arm-none-eabi),对于AArch32社区里更喜欢称它为ARM32,同理AArch64也就是ARM64。bare-metal是裸机的意思,即没有操作系统,也就是我们用的是它编译出的二进制文件,而不是.exe、.elf等在操作系统上执行的可执行文件。arm-none-eabi是个常见的名称,arm是指它的目标平台(target)是ARM32架构的,none是指没有厂商,表明这个工具链不是某个厂商开发的,另一种解释是"none"意味着没有特定的操作系统环境关联,即裸机,eabi是Embeded(嵌入式)API(二进制程序接口),即Embedded Application Binary Interface的缩写,表示使用的是嵌入式的二进制接口标准
现在大家的电脑普遍都是x64架构,使用32位程序运行,速度会有所受限
因此我们可以往下划,选择这个64位的工具链
下载过程很慢,可以使用IDM下载(某吾爱)。下载完成后,找一个纯英文路径的地方解压,解压后名称可以根据自己的习惯修改一下
3,配置工具链
现在回到CLion,在设置里找到【构建部署】,在里面选中工具链,默认情况下只有MinGW一条工具链
按照箭头指示可以添加一个新的MinGW工具链。
名称随意,起名x64-arm-none-eabi或者MinGW64-ARM32都行,只要方便自己寻找即可。名称下方的工具集是指MinGW这个项目,前面没提到过gcc是gnu项目,所以是在linux下使用的,Windows并不能直接使用,而Windows想要使用它就需要通过MinGW这个项目。这里的MinGW是CLion自带的工具集,新一点的版本是MinGW12.0,对于我们的STM32开发,区别不大,使用默认的即可
cmake是一种构建系统,它通过下面的构建工具实现自动化构建,不需要每次手动输入一大串参数来编译文件,只需编写cmake脚本即可
接下来就是C和C++编译器,也就是我们的重点,由于使用的是CLion默认的MinGW工具集,那么这里的C和C++编译器都是MinGW里自带的gcc编译器,只能编译出主机的可执行程序,我们只要把这两个替换为我们刚下载的交叉编译器即可。点击旁边的文件夹,选择之前我们解压的路径,在它里面找到bin目录,在bin目录里找到gcc
g++同理
配置好后,它会让我们配置cmake的选项,当然这是对于已经有了CMakeList.txt的工程,没有的可以先跳过,后面再配置。工具链选择我们刚才创建的arm-gcc,下面的构建选项可以看到是-j 14 ,这个意思是使用14核并行构建,CLion的默认的并行构建核心数是电脑最大的核数-2,显然我的电脑核心是16
之后,CLion会自动加载cmake工程
此时,点击小锤子可以进行构建(如果工程能构建的话)
4,OpenOCD
工程只能编译还不行,因为STM32开发是需要把程序烧录到开发板上的,因此需要一个工具可以与仿真器(比如ST-Link)进行通信,从而进而开发板的烧录与调试。这里需要注意,OpenOCD对J-Link的支持并不好,对于STM32开发,能使用ST-Link就使用ST-Link
OpenOCD:0.12.0
-> 选择下图这个压缩包Download OpenOCD for Windows ,选择第一个即可
下载完成后,找一个纯英文的路径解压
这里可以不添加到系统的环境变量上,直接在CLion里指定路径
指定OpenOCD可执行程序
然后点击测试,如果能通过,说明下载的没有问题
5,安装STM32CubeMX
想要在CLion创建STM32工程,需要使用到STM32CubeMX,它会自动生成一个模板工程供CLion开发,从这个工具上,我们可以获得我们需要的cmake工程。安装过程此处不赘述,安装完成后,在前面那个界面指定路径,然后案件测试即可。而Stm32CubeCLT是新出来的选项,我是指CLion以前没有内置这个选项,暂时没用过
遗弃内容(可跳过):
2024.9.22
博客vip了,那就再推荐一篇Windows 11安装 MinGW-w64 教程,也可自行查找。不过教程里的网站似乎不太好用,可以到github里下载Releases · niXman/mingw-builds-binaries
选择x86_64-14.2.0-release-posix-seh-ucrt-rt_v12-rev0即可,x86_64表示的是主机为64位的架构,posix是类Unix的API标准,seh是 Windows 特有的异常处理机制,ucrt是更现代的 C 运行时库arm-none-eabi-gcc:13.2
-> 这个必须得说明一下,之前我下载的交叉编译工具链也是2019版的,但那个实在太老还停产了,构建时提示只支持11.0及以上的
2024.12.16
前几天发布了14.2版本的,这个版本可以使用C++20的module特性了,个人开发的话,如果想要体验一些C++新特性的话可以选择这个版本
二、基础搭建 ଘ(੭ˊ꒳ˋ)੭
1,无名
这块内容已经舍弃,留下的添加系统环境变量还可做参考。这个环境变量可加可不加,因为在CLion里已经指定了交叉编译器和OpenOCD的路径了,CLion会自己把这些参数传递给相应的工具,而不需要从系统的环境变量里获取
2,配置系统Path
先按一下Win+V观察剪贴板是否启用,再依次按Ctrl+C把交叉编译工具链和OpenOCD下的bin的路径复制一下
然后,正式开始配置系统Path,打开【高级系统设置】-【环境变量】-【编辑】
一定要注意的是,打开的是系统变量下的Path,而不是上面那个用户变量!!!
然后,双击空白处,按Win+V,剪贴板上就会有刚刚复制的三个路径,填入即可
填完后要按三次确定,配置完后要重启,避免识别不到系统路径
3,检验
重启后运行cmd,分别执行下面代码检验是否系统路径配置成功。如果不成功需要再检查一下前面系统变量的配置,并尝试重启,因为有时候即便过程全对也会出现识别不到的现象
arm-none-eabi-gcc -v gcc -v
三、创建工程 (꒪⌓꒪)
1,创建项目
完成配置CLion后选择新建项目,路径和名字不要有中文!这里以STM32F407为例,点击新建项目
选择嵌入式STM32CubeMX,新版与旧版有所不同了,旧版是CLion自动打开STM32CubeMX,然后由我们自己配置。现在需要先使用STM32CubeMX主动新建一个工程
那这个过程就更加简单了,我们可以先直接打开STM32CubeMX生成一个STM32CubeIDE的项目,然后使用CLion打开这个工程;也可以在这个界面启动STM32CubeMX,配置完成后再继续。
现在按照上面的提示,我们点击箭头所指
可能需等待一段时间下载软件包
下载完成后在左上角输入我们的芯片STM32F407VET6,可以看到现在有VET6和VET6TR两个型号,我们双击上面的VET6即可。为了下次勾选方便可以点击收藏
接下来就是我们熟悉的STM32工程创建步骤,一成不变的SYS、RCC和时钟树的配置。首先,在SYS里把Debug配置为串行,下面的时基可以选择默认,也可以选择其他时钟。如果后续需要FreeRTOS等,那么系统滴答计时器systick会被占用,需要另寻一个时基,一般可以选择TIM7这种基础定时器。
在RCC这里把HSE和LSE都配置为外部晶振
接下来切换时钟发生器选项卡,勾选红框所示,这个步骤是配置HSE这个高速时钟,通过锁相环产生合适频率的主频时钟
接下来是输入外部晶振时钟频率,这个频率一定要对照一下自己手上这块板子的原理图,尽管芯片一样,但板子上的外部晶振就不一定了,有些是25MHz、8MHz,我这里是12MHz
设置好外部晶振频率并选择好通路后,直接在右侧输入想要达到的最终频率,再按一下回车,它会自动配好
配置完成后,点击箭头方向
输入名称STM32F407和路径(这里路径不小心输入重了),指定Toolchain/IDE为CMake(旧版CLion和旧版的STM32CubeMX需要指定为STM32CubeIDE),它创建的工程就是cmake工程了。需要注意这里有个bug,如果先指定IDE,那么后面就无法再指定路径了,必须重新创建工程,也不知道这个bug什么时候出来的。如果先输入项目名称,也有可能触发错误,所以最好先指定路径,要么等待ST把这个混账错误修复
这里的路径变灰是因为按了保存,不用在意
点击箭头方向,进入代码生成器的界面,勾选上,让它生成的代码.h/c分离开来
此时,按下Ctrl+S,保存。可以看到路径变成灰色了,然后点击箭头方向,生成代码
首次使用是需要登录的
只不过不知道这是新版的bug还是单纯的网络不好,挂了梯子也会提示网络错误
如果弹出网络错误窗口的话,点击上方的Help,然后选择Conection & Updates,选择myST选项卡,然后点击账号登录。如果这个也失败了,那么就再换成点击生成代码的那个窗口,从那个地方来。要么等几天再试一下
然后会进入这个界面,需要等待一段时间,然后输入账号进行登录。总之生成工程后,相应目录里会出现.ioc文件
可以看到生成后的工程目录
在CLion里就可以检测到,并且【继续】变成可点击的状态
2,配置工程
后面的内容是以前基于旧版的CLion完成的,虽有差别,但也不影响操作。后面内容有时间会重新更新一遍
在项目根目录新建一个后缀名为cfg的文件,我用的是仿真器是STLink(-V2),所以起名为stlink.cfg,板子是stm32f407VET6,配置如下(稚晖君那有更详尽的配置)
source [find interface/stlink.cfg] # 其中下面这句不要省略,不然很容易出现连接不上的情况 transport select hla_swd # 内存大小 这里我的是512kb,就是80000 # set WORKAREASIZE 0x80000 # chip name 改为相应型号 # set CHIPNAME STM32F407VET6 source [find target/stm32f4x.cfg]
同时要说明的是J-Link的配置有些特殊,因为OpenCD无法直接识别J-Link,需要把J-Link驱动改为串行接口,具体操作。实测过程中,只需到zadig改一下就好了(需要插拔),当然不同人情况可能不同。
然后点击上方的编译配置
点击添加,并选择OpenOCD
把这个cfg替换成我们新建的那个stlink.cfg
回到主界面,打开设置,找到CMake的构建选项,把工具链换成我们之前配置的交叉编译工具链
回到主界面,点击左下角的cmake选项,然后点击刷新
然后就会发现什么反应都没有,不用怀疑,这是bug,将来也许就修复了。所以需要重启一下CLion,它会自动刷新cmake,然后弹出CMake的构建选项,这里的工具链仍然是之前的交叉编译工具链,所以不用改
如果前面跟我一样没有添加环境变量,那么这里会报错
解决方法也很简单,要么添加工具链的环境变量,要么像我这样,把这里的工具链指定全部注释掉。我个人更喜欢后面这种,因为使用的是CLion,我们已经在CLion的工具链配置选项里指定了编译器,那么就没必要再从环境变量里获取了,除非使用的是VS Code之类的IDE。
当然,如果对STM32CubeMX依赖很大,也就是开发过程中需要使用STM32CubeMX多次生成工程,那么这里的cmake自然也会被重新生成,那么就需要重新注释,这显然很麻烦,那么设置环境变量是更好的方法。
然后重新刷新cmake即可
再次进入运行配置的界面(主界面的上方,点击编辑配置的那个),可以看到这里出现了CMake应用程序的运行配置,说明CMake加载成功了。同时在OpenOCD这个运行配置里的目标,出现了STM32F407和STM32_Drivers这两个构建目标,前者是我们这个工程的可执行构建目标,后者是HAL库的
指定目标后,这个名称可以改。正常情况下,如果一开始CMake就刷新成功的话,那么创建OpenOCD程序时就可以指定构建目标,那么名称就自然而然跟着刷新。
注意到此时多了一个锤子,点击锤子就可以开始构建了,构建过程会出现相应的输出信息。锤子旁边依次是运行和调试
连接上板子,点击调试按钮,就可以对板子进行烧录和调试了
遗弃内容
旧版CLion会弹出选择配置cfg文件,这里之所以需要配置文件,是因为使用到了Open OCD烧录程序
点击左下角那个长得像方舟的符号,再点击它旁边那个长得就像刷新的东西,没有红字即为正常
然后点击最上面的那个小锤子,构建成功后会冒绿光并生成两个蓝色的文件,这一步很关键。如果构建工具是Nija(前面选择的),那么构建时不会弹出有颜色的输出信息
(下面的窗口拉长了一点,为了方便观察)
这时如果你可以配置烧录配置文件了,没有连接板子或者连接了但没安装相应仿真器驱动就直接烧录(长得像运行的那个按钮),会出现含failed的红字。
接上仿真器,点击烧录,再点击旁边那只爬虫就可以得到一堆红字,最上方会变成重新调试和停止,同时仿真器也会闪闪发光。
然后就可以设置断点什么的了,至于查看寄存器svd什么的其他博客里也有
嗯~,出乎意料,这不知道怎么回事就进去硬件错误里了
注释掉了之后就能正常运行了???原因未知,先记录一下,方便以后定位
详情请见目录【七、某些BUG】-【卡死在SystemClock_Config里的Error_Handler】
【锚点】:系统时钟配置的隐患
【半解决】:按一下板子上的重置键,或者使用OpenCD自带的重置MCU
【半解决+1】:在Open CD的配置文件中重置选项勾选运行
7,移植库或文件【待更新】
这个是比较简单的,要么在CLion项目打开的情况下,直接该替换替换,该删删,之后修改CMakeLists。要么直接把原有的工程文件夹拖到关闭项目后的CLion里,点击一下信任,再把之前用STM32CubeMX新建工程时产生的CMakeLists和启动文件复制粘贴过来。总之,多试多查,不怕犯错,但前提是要把重要工程都备份好。下面内容建议先了解一下CMake构建的过程
①半移植
即在原有基础上修修补补改改,原目录如下
现在开始移植
如果此时你以无需使用STM32CubeMX,那么与其有关的文件可以删掉,注意.idea不建议删掉(但也无所谓,顶多弹个框按一下确定),两个ld文件、Startup文件、syscalls.c和sysmem.c不要删除,其余自便。Startup文件、syscalls.c和sysmem.c都放到CORE里了
CLion里面如下,main文件其实放在USER/src或者USER/下比较好,放在外面等会又需要在CMakeLists多写一句
CMakeLists里面其余的可以先不了解,值得关注的是下面这三句
#这一句是包含头文件目录,只需把头文件目录包含进来即可 #目录之间需要用空格(换行也行)隔开,写文件名时CLion会自动补全 include_directories(Core/Inc Drivers/STM32F4xx_HAL_Driver/Inc Drivers/STM32F4xx_HAL_Driver/Inc/Legacy Drivers/CMSIS/Device/ST/STM32F4xx/Include Drivers/CMSIS/Include) #这是添加预定义,-D后面紧跟着预定义的内容即可 #这一句就加了DEBUG、USE_HAL_DRIVER、STM32F407xx这三个预定义 #一般不需要改,看个人需求,与.h里的#define并无二致 add_definitions(-DDEBUG -DUSE_HAL_DRIVER -DSTM32F407xx) #下面这一句是包含所有要编译的文件,包括启动文件 #其中GLOB_RECURSE表示使用通配符“*”,SOURSES是个变量名,可以理解为file变量 #使用了通配符需要用双引号括起来,“Core/*.*”表示包含Core目录下的所有文件 #注意!如果你是从Keil或IAR移植过来的,切忌不要把它的启动文件之类的带过来,只要带c相关的文件即可 file(GLOB_RECURSE SOURCES "Core/*.*" "Drivers/*.*")
根据刚才的目录,可以改为下面这样
include_directories(CORE/inc CORE/system DATA USER/inc DRIVER/inc) add_definitions(-DDEBUG -DUSE_HAL_DRIVER -DSTM32F407xx) file(GLOB_RECURSE SOURCES "CORE/*.*" "DRIVER/*.*" "DATA/*.*" "USER/*.*" main.cpp )
改了CMakeLists之后Cmake需要重新加载
如果你是使用C/C++混编,那么就要格外小心了,比如调用C++里调用C函数等
既是混编,那么一定要确保.c对应的头文件里一定要包含extern "C",否则C函数以C++的方式编译,那么就会导致链接时找不到对应函数符号。
如果出现下面错误,可以翻阅这两篇undefined reference,undefined reference to `_sbrk‘,主要原因是前面移植时没有把系统调用文件syscall和系统内存sysmem添加到项目里。
根本原因就是arm-none-eabi默认是使用startfiles,且编译器为了节省空间,删减了一部分代码, 导致有些方法只有声明没有实现,需要自己实现。
这一部分的具体解决方法在下面的目录【开发拓展-使用C++开发-找回编译器缺失的部分】
②完全移植
现以一个IAR工程为例
把整个文件夹移到一个纯天然目录(路径上不含任何中文、空格之类的),同时文件夹改成纯天然符合规范的名字
先删无关目录,如settings目录、Debug目录等。再删除原工程配置文件,如启动文件
再找到STM32CubeMX生成的配置文件,共7个,如下
上图还漏了一个sysmem.c文件,不然会报错_sbrk,其中STM32F407VETX_RAM.ld可不加,因为CMakeList里并没有链接它
复制到相应位置后,就可以在此工程右键打开CLion了
进去后,如下,确定即可,如果你不需要选择其他工具链的话
然后,打开CMakeLists.txt文件,重点是改下面三个,改法同方法①
然后改工程名字,poject里的第一个参数,改为你需要的名称(我这里是把DoJi改为BuJbDmji)
补充一句,如果你更换芯片了,那么这个ld文件也要改名
改完之后就可以重新加载CMake了,如果报错,自己再看看是不是某个文件夹忘加了或者工程里还有不必要的文件没删。根据提示,多试试
加载成功后,上方的名字也会改变
此时就可以构建了,至于添加Open CD之类的就不赘述了
总结:
1,复制工程目录
2,删不必要文件
3,修改CMakeLists
四,开发体验提升 (⑅˃◡˂⑅)
由于是更新多次,所以工程项目会经常替换,名字不一样很正常
1,查看寄存器
①下载
a.到STM32CubeMX里下载
点击【Help - Docs&Resources - System View Description】,就可以等待下载了
不过运气比较糟,下载了三次它还是没有回应
b.到官网去下载(推荐)
进入ST下载官网,在输入框内填上相应型号,然后经历漫长的等待,下载那个SVD
2024.10.20:
现在情形恰恰相反,官网会卡半天,CubeMX上几秒便可下载完成
②添加SVD
下载完后,为了方便添加以及后续可能出现的更换等等问题,于是在CLion的目录下新建了一个user_resources,里面专门用来添加各种包的。考虑到以后CLion可能会用于别的用途,那就再新建一个子目录MCU,在这里添加刚刚下载的SVD包,并解压
现在回到CLion,到相应调试窗口下,点击外设一栏,然后点击加载.svd文件,找到相应型号
选择第一个全都要或者挑选几个按需选
这里选择全都要,当然会冒出一堆外设,然后点击最外面的那个STM32F407.svd的,让它收起来,这样再展开时,所有外设都会默认收缩起来
2,开发改善
① 背景
打开所有设置或设置,然后【外观-背景图像】,选择想要的图片,确定即可
②去除特定警告
把光标放在报错位置,点击更多操作
2024.10.6
后面好像没有更多操作这个选项了,可以直接在设置里搜索检查,然后找到数据流分析,把无尽循环去掉
点击编辑检查配置文件设置
取消勾选,确定即可
3,一些插件与调试方法
① 调试方法-博客
② 2023年Clion插件推荐
③ 自动格式化代码
两种办法,一种是直接用调项目设置,另一种是使用Clang-Format。前者适合个人开发,因为更改项目设置只能应用到本地。后者适合团队,因为Clang-Format是个配置文件,可流转
对于第一种办法,找到下面设置,自行摸索即可
至于另一种,可以参考博客Clang-Format用法详解,下面是博客里的配置,挺像Java风格的
#Generated from CLion C/C++ Code Style settings
BasedOnStyle: Google
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: "^ NOLINT:"
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: "^<.*"
Priority: 2
SortPriority: 0
- Regex: ".*"
Priority: 3
SortPriority: 0
IncludeIsMainRegex: "([-_](test|unittest))?$"
IncludeIsMainSourceRegex: ""
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- "c++"
- "C++"
CanonicalDelimiter: ""
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ""
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Cpp11
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
但是显而易见,每个人的代码风格都不一样,而对于没有接触过Clang-Format的人来说,学习成本略高。所以下面带来了如何使用AI配置(有时候它只能告诉你在准确位置,至于属性对不对试了才知道),示范如下
①打开某个网页版AI,如文心一言、智普清言等
②先写下诉求,并把Clang-Format代码复制过来(这个随意,只要描述清楚,代码可以不必复制)
③现学现用
配置和效果如下
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never
4,示例工程
语音存储与回放(串行)_页写入_stm32f407VET6_非正点或野火实验平台_裸机(遗弃)
5,ITM与SWO
简单来说就是打印输出,没有打印输出的调试是不完整的
单片机的调试一般都是使用串口打印,虽说有了仿真器后可以像调试本地程序一样断点运行,但如果需要测试的数据量比较多,那么还是需要串口打印来记录的。除非像TI官方那样,CCS与TI板相互配合,有独特的调试方法
很遗憾我手上这块只有一个仿真接口,串口被仿真器占用掉了,连USB都没有,所以就想试试仿真器自带的打印功能。之前打印使用的是IAR的Terminal I/O,转到CLion后就完全不知道怎么做了
但为了不影响开发,可以姑且用平(?次)替ST-LINK Utility 试一试,如果你使用的是其他仿真器可以先查查有没有SWO模式之类的
【优点】:配置极为简单,速度还行
【缺点】:无法与CLion进行联合调试
需要加入头文件<stdio.h>,在合适的位置加入下面HAL自带的ITM_SendChar(char );函数就行了,输出内容为字符,自定义即可。要注意的是这个东西需要添加头文件"stm32f4xx.h",不要直接使用"core_m4.h",不然找错误会超乎想象
然后编译,烧录到板子上
完成后,可以到官网下载
下载完成后,就可以打开了
点击上方工具栏的【ST-LINK】-【Printf via SWO viewer】
或者点击一下这个示波器样的按钮
然后设置好System clock,这个要根据你自己的时钟树来设置,我的是168MHz,在前面STM32CubeMX关于时钟树配置中有(我已经运行过,所以下面会有输出,正常是空白),端口默认
按下start就可以直接运行已经烧录在MCU的程序了,但此时CLion是无法调试、下载的,被 ST-LINK Utility占用了,除非把 ST-LINK Utility关上。二选一的调试方法让人 (//̀Д/́/)
需要说明的是,只发送char是远远不能满足我们的需求的,还需要对printf进行重定向
①先打开syscalls.c文件(如果你没有,参考一下目录【使用C++开发-找回编译器缺失的部分】)
②找到 _write
③改为下面这样
ITM_SendChar((uint32_t)(*(ptr++)));// 加个括号防止歧义
2025.1.11
现在补上这个功能STM32使用ITM调试_通过仿真器实现串口打印-CSDN博客,不过这个调试似乎很容易让ST-Link报废,具体情况暂不明
6,集成的Git
CLion中已经集成了Git,可以很方便的使用Git。具体操作可以参考一下下面的这个视频Git/Gitee可视化版本管理—使用CLion加速STM32Cube项目开发_哔哩哔哩_bilibili
2024.10.6
得要学会远程仓库的管理,不然后面出现什么变故导致本地仓库没了,追悔莫及
7,打开汇编
右键代码,点击【显示程序集】,即可打开汇编代码
上方会出现一行命令,通过修改参数以查看不同情况下编译出的代码。比如,把-Og改为-O1,从调试级别变为了1级优化,再点击一下旁边的刷新 ,就可以看到一级优化下的汇编指令
2024.10.6
?!这是什么鬼?我都不记得有这回事了,一直没用这个功能
五、开发拓展 ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚
1,使用C++开发
a.编程规范
①无论是c文件还是其对应的头文件都不要直接调用Cpp对应的头文件,不然会编译错误。网上是有如何在C中调用C++的,但还是不建议这么做。因为C++作为C的超集是兼容C的且功能更强大,适合当主体(main.cpp)使用。即便不使用模板、智能指针等泛型编程的东西(当然不一定带得动),重载、引用类型、bool类型、类的继承等就已经可以大大提升开发体验和效率了。
②在cpp文件或其头文件里调用C头文件时,直接用extern “C”包含需要的头文件即可,其实绝大多数都不必包含,因为HAL里已经做过了。
③定义中断服务例程和中断回调函数一定要用extern "C"括起来,不然可能会识别不到
b.找回编译器缺失的部分
和前面的移植一样,STM32CUbeMX生成的ld文件和启动文件都要复制过来,
除此之外!!还要把下面这个syscalls.c和sysmem.c文件复制过来
如果编译报错,缺少什么什么__sbrk之类的函数,那么就要在里面加上这些函数的实现方法(如果不适合,可到网上查这些函数的实现)
先是全局变量
extern int errno;
extern int _end;
然后是实现方法(函数)
caddr_t _sbrk(int incr)
{
static unsigned char *heap = NULL;
unsigned char *prev_heap;
if (heap == NULL)
{
heap = (unsigned char *) &_end;
}
prev_heap = heap;
heap += incr;
return (caddr_t) prev_heap;
}
下面是完整代码供参考
/**
******************************************************************************
* @file syscalls.c
* @author Auto-generated by STM32CubeIDE
* @brief STM32CubeIDE Minimal System calls file
*
* For more information about which c-functions
* need which of these lowlevel functions
* please consult the Newlib libc-manual
******************************************************************************
* @attention
*
* Copyright (c) 2020-2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* Includes */
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
/* Variables */
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));
extern int errno;
extern int _end;
char *__env[1] = { 0 };
char **environ = __env;
/* Functions */
void initialise_monitor_handles()
{
}
int _getpid(void)
{
return 1;
}
int _kill(int pid, int sig)
{
(void)pid;
(void)sig;
errno = EINVAL;
return -1;
}
void _exit (int status)
{
_kill(status, -1);
while (1) {} /* Make sure we hang here */
}
__attribute__((weak)) int _read(int file, char *ptr, int len)
{
(void)file;
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
*ptr++ = __io_getchar();
}
return len;
}
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
(void)file;
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}
int _close(int file)
{
(void)file;
return -1;
}
caddr_t _sbrk(int incr)
{
static unsigned char *heap = NULL;
unsigned char *prev_heap;
if (heap == NULL)
{
heap = (unsigned char *) &_end;
}
prev_heap = heap;
heap += incr;
return (caddr_t) prev_heap;
}
int _fstat(int file, struct stat *st)
{
(void)file;
st->st_mode = S_IFCHR;
return 0;
}
int _isatty(int file)
{
(void)file;
return 1;
}
int _lseek(int file, int ptr, int dir)
{
(void)file;
(void)ptr;
(void)dir;
return 0;
}
int _open(char *path, int flags, ...)
{
(void)path;
(void)flags;
/* Pretend like we always fail */
return -1;
}
int _wait(int *status)
{
(void)status;
errno = ECHILD;
return -1;
}
int _unlink(char *name)
{
(void)name;
errno = ENOENT;
return -1;
}
int _times(struct tms *buf)
{
(void)buf;
return -1;
}
int _stat(char *file, struct stat *st)
{
(void)file;
st->st_mode = S_IFCHR;
return 0;
}
int _link(char *old, char *new)
{
(void)old;
(void)new;
errno = EMLINK;
return -1;
}
int _fork(void)
{
errno = EAGAIN;
return -1;
}
int _execve(char *name, char **argv, char **env)
{
(void)name;
(void)argv;
(void)env;
errno = ENOMEM;
return -1;
}
至此,也算得上优雅地使用CLion了
2,使用FreeRTOS
如何在STM32CubeMX中配置FreeRTOS的教程网上有很多,不多言了。值得关注的是配置完后,要修改一下CMakeLists,把下面图中22到24行的注释取消掉(大家的行数可能不一样),因为生成的CMakeLists是默认关闭硬件浮点(FPU)的
另一方面,CLion里集成了FreeRTOS的相关调试,这个功能非常地好用。除了要启动这个功能外,还需要配置一下工程,可参考博客【嵌入式CLion】进阶调试
六、一天写不了几行BUG ( ´◔︎ ‸◔︎`)
有些由于忘了截屏,只能简单描述一下
3,显示“不是有效的win32应用程序”
编译能过,但调试时显示运行 “'.elf' 时出错createprocess error=193, %1 不是有效的 win32 应用程序”之类的,这是因为CLion默认构建的是CMake程序,也就是它把编译出的ARM32架构的可执行程序当成了主机的可执行程序,所以需要创建一个OpenOCD运行配置
4,只有一个报错,并指向了蓝色的flash.id文件
进去一看有个提示,说什么【“ready”这个关键字只支持11.0以上的gcc,如果你有10.0的,请把它删除】。
-》按照本教程里的安装,下个11.0版本及以上的arm-none-eabi-gcc,然后配置系统变量,加到工具链什么的
5,出现了完全摸不到头绪的提示
某次重装系统后,使用CLion编译工程报出大量错,再次通过重装系统解决就是后话了。在大量错误里唯一能找到价值的还是标出的那一句
“ "F:\JetBrains\CLion 2023.1.1\OpenOCD-20231002-0.12.0\bin\openocd.exe" -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -s F:\JetBrains\CLion 2023.1.1\OpenOCD-20231002-0.12.0\share\openocd\scripts -f D:\EDA\MCU\untitled1\stlink.cfg -c "program D:/EDA/MCU/untitled1/cmake-build-debug/untitled1.elf" -c "init;reset init;" -c "echo (((READY)))" Open On-Chip Debugger 0.12.0 (2023-10-02) [https://github.com/sysprogs/openocd] Licensed under GNU GPL v2 libusb1 09e75e98b4d9ea7909e8837b7a3f00dda4589dc3 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Unexpected command line argument: 2023.1.1\OpenOCD-20231002-0.12.0\share\openocd \scripts GDB Server stopped, exit code 1 进程已结束,退出代码0”
这个问了一下智谱AI 4.0,还真给回答出来了,于是我就照着做也确实解决了。这也许是一个不错的思路
tips:目前个人感觉是【通义前问2.5 >>?ChatGPT4.0 > 智普AI4.0 >> ChatGPT3.5】,单从免费上来看,这就很明显了。
——2024.5.10
这就是前面为什么不让安装目录上出现空格和中文
6,卡死在SystemClock_Config里的Error_Handler
这个比较莫名奇妙,即便外部时钟源是按原理图配置的,使用IAR时能正常起振,这里却会死循环。
把SystemClock_Config里的Error_Handler直接去掉是可以运行的,但治标不治本的办法只是遮盖了失败的初始化,时钟频率只有正常的一小半。这都是用STM32CubeMX配置的,不应该有问题才对,同样的代码用IAR就没有问题,原因未知
无意中按了一下板子上的重启键就正常了,这一点让我想起了IAR中Debugger下关于ST-Link的配置,是有什么硬件复位、系统复位的。试了一下CLion自带的重置MCU,发现也可以使HSE正常初始化
由此推测cfg配置、GDB什么的存在某些问题没配好,希望有大佬可以讲解一下。
实在没有解决办法的话,只好把这个重置MCU当成程序运行键,毕竟IAR烧录完了之后还需要按一下运行键,这样似乎也不错?
把重置改为运行即可,其它教程好像不需要这样做,难道是我的配置和板子特殊?
7,出现一堆关于启动文件报错的红字
注意哈,移植时不要把IAR或Keil的启动文件也带进来了,启动文件要用STM32CubeIDE(MX)的,也不要把原项目工程的工程配置如.icf带过来,只要.c .h .cpp之类的文件即可
曾闻:硬件开发嘛,就是要反复折腾,折腾得多了,水平自然就提高了
更多推荐
所有评论(0)