做嵌入式开发,一定对Keil、IAR之类的远古IDE不陌生,可它俩那与现代IDE格格不入的远古画风让人脑壳着实发疼。好一点的解决方案就是IAR、Keil、Quartus ii等联调VS Code,即代码在VS Code编写(调试),在IAR或Keil编译烧录。以VS Code强大而丰富的插件功能和简约优美的画面,已经使开发体验有了大幅的提升,而这里则提供另一种更为一体化的开发思路(缘于稚晖君大佬的博客)
同时由于CLion不像IAR、Keil那样有完善的嵌入式的工具链,所以手动配置工具链,流程又长又容易出错。如果某个环节没有如教程那样复现,那将会是顶级的折磨,所以要做好心理准备和取舍,这里就先祝愿大家一步到位。
需要说明的是:
1,本篇面向的是了解过HAL开发的同志,可能相当啰嗦,篇幅也较长(主要是避免以后自己看不懂),建议按需查目录。同时由于水平有限,难免会有一些冗余的步骤和谬误,还望理解。
2,第一次配置工程会很麻烦,不过以后配置工程时就会轻松许多,直接复制原工程再改(需要学会编写CMakelist,只是稍许麻烦哦,三行语句而已)。
3,后面会给出几个配置CLion过程中遇到的问题,解决或避免的方法都内嵌在教程里。如果你已熟稔配置流程,却仍出一些问题,可以跳到最后看看有没有可以参考的例子,
4,以下内容是基于Windows操作系统的搭建过程,若是其他操作系统,希望本篇能提供一点参考和帮助。(网页端的目录在左侧←)
5,如果你想使用C++,同时对CLion新建工程已经了然如胸,那么可以不必看这些冗余的内容,直接根据目录,跳转到目录五下的使用C++开发。
一、工具准备 ૮₍ ˃ ⤙ ˂ ₎ა
本着版本越新越好的原则(虽然实际上往往会出现各种令人不快的兼容性问题),这里的版本都尽可能地为当下最新版
CLion:2023.3.3
-> の~~
STM32CubeMX:6.9.0
-> 到官网下载最新版即可,不必在意有没有SW4STM32选项(6.4.0版前是有的)
MinGW:13.2
-> 这有大佬的博客可以参考,MinGW-W64 下载、安装与配置
arm-none-eabi-gcc:13.2
-> 这个必须得说明一下,之前我下载的交叉编译工具链也是2019版的,但那个实在太老还停产了,构建时提示只支持11.0及以上的,当时没注意花了一夜才搞明白。。。快点我快点我
这里简单解释一下为什么用交叉编译工具链,我们知道嵌入式开发一般是基于arm架构的,但是我们的电脑一般都是Windows系统,那么在Windows下编译的工程最终会生成exe可执行文件。而这个可执行文件只能在Windows系统下运行,它包含了二进制文件(x86的机器码),但也包含了一些关于Windows操作系统的信息,并非纯二进制文件。
而Windows可以根据这个exe文件来为其分配内存之类的,让其在设备上运行。对于非Windows系统,甚至是裸机的嵌入式微处理器而言,这自然是不能在上面直接运行的。而且由于两者不是同一个架构,即便是纯二进制机器码也不能运行。
所以,这就要使用交叉编译器。交叉嘛,顾名思义,即宿主平台(你的电脑)与目标平台(嵌入式系统)不一致,可以将宿主平台代码转为目标平台。后面你会发现工程产生的文件是elf,而非平时遇到的exe,那么这个elf是什么呢?elf其实可以粗略地理解为linux下的可执行文件,既然elf和exe都是操作系统下的可执行文件,那为什么exe不行,elf就行呢?这是因为elf可转为其他平台架构下的二进制码。
如果你做安卓开发,那么你应该选用安卓交叉编译工具链。嵌入式开发自然要选择嵌入式的交叉编译工具链,这里为什么选择GNU呢?那是因为它十分地牛掰,牛掰之处可自行查询。
那么接下来解释一下为什么要用到这条工具链,我们从名称分析
arm-gnu-toolchain-13.2.rel1-mingw-w64-i686-arm-none-eabi.zip
arm-gnu-toolchain:适用于arm平台架构的gnu工具链
13.2.rel1:13.2是版本号,rel1是发行版号(release 1)
mingw-w64-i686:MinGW-w64是一个项目,提供了在Windows上使用GCC和其他GNU工具开发原生Windows应用程序和跨平台应用程序的能力。它包括对64位和32位Windows的支持。i686指的是32位的x86架构,这是较旧的Windows PC常见的CPU类型。
arm-none-eabi:表示该工具链用于编译代码到ARM架构的目标平台上,"none"意味着没有特定的操作系统环境关联,即裸机,而"EABI"是Embedded Application Binary Interface的缩写,专为嵌入式系统设计,通常用于无操作系统或轻量级操作系统如FreeRTOS、μC/OS-II等的开发。
后面有空我会发进阶部分,通过WSL来使用linux平台下的arm开发,由于宿主机平台不一样,所以到时候会换一条工具链。
OpenOCD:0.12.0
-> 选择下图这个压缩包Download OpenOCD for Windows (gnutoolchains.com),选择第一个即可
二、安装 ଘ(੭ˊ꒳ˋ)੭
1,安放
CLion、STM32CubeMX安装就不赘述了,其余三个解压在同一个目录下即可(忽略起错名字的父目录MinGW-64,mingw64那个好像是安装得到的,其余两个是直接解压)。
注意哈,最好改一下名字,整个路径都不要有空格、中文什么的,不然构建时可能会出现十分离谱的事情
2,配置系统Path
先依次按Ctrl+C把三个文件夹下的bin的路径复制一下
然后,正式开始配置系统Path,打开【高级系统设置】-【环境变量】-【编辑】
一定要注意的是,打开的是系统变量下的Path,而不是上面那个用户变量!!!
然后,双击空白处,按Win+V,剪贴板上就会有刚刚复制的三个路径,填入即可
填完后要按三次确定,配置完后要重启,避免识别不到系统路径
3,检验
重启后运行cmd,分别执行下面代码检验是否系统路径配置成功。如果不成功需要再检查一下前面系统变量的配置,并尝试重启,因为有时候即便过程全对也会出现识别不到的现象
arm-none-eabi-gcc -v
gcc -v
三、配置CLion (꒪⌓꒪)
除了自搭工具链外,官方还有IAR工具链的搭建方法。
1,搭好工具链
打开所有设置(如果英文不习惯,在下面的插件/Plugins里可安装中文插件Chinese什么的)
找到工具链,为了避免混乱就直接在默认的环境上配置(很容易改回去的)
a.把工具集换成自己下载的那个mingw64的目录(不需要到它的下级目录bin),其实用它绑定的也行,据说还更快一些,不过mingw-32-make生成的提示自带百分比显得更美观
b.把构建工具、C编译器、C++编译器改为自己的相应程序路径(照葫芦画瓢即可)
c.其余都不必改,以防出现不可知的错误
2,确认CMake
反正是用默认配的,工具链只有一个所以选哪个都一样。但还是换一下比较好,权当心理安慰
3,配置程序的位置
找到你自己程序的安装位置即可,按一下测试,冒绿字即可
做好这一切之后,记得按确定!!
四、创建工程 ಠ_ರೃ
1,创建项目
完成配置CLion后选择新建项目,路径和名字不要有中文!这里以创建“jk触发器”为例
2,检验初始化工程(含cfg)
(第一次配置工程时,最好做这一步。正常新建工程,由于工具链已配好,这一步的检验过程是可以省略的)
初始工程默认配置的是STM32F030F4PX芯片,且正常情况下一定是可以通过编译的, 这里之所以要进行这一步,就是为了确认工具链之类的是否搭好,避免后面STM32CubeMX都配好了,结果却不能编译调试的悲催情况。
配置文件先随便选一个或跳过都行,这里先随便选一个。
点击左下角那个长得像方舟的符号,再点击它旁边那个长得就像重新编译的东西,没有红字即为正常
然后点击最上面的那个小锤子,构建成功后会冒绿光并生成两个蓝色的文件,这一步很关键
(下面的窗口拉长了一点,为了方便观察)
【这一步的检验过程很长,需要很大的耐心。而且这一次只是体验一下过程,后面可能又要重新配置】
这时如果你可以配置烧录配置文件了,没有连接板子或者连接了但没安装相应仿真器驱动就直接烧录(长得像运行的那个按钮),会出现含failed的红字。
这里可以新建一个后缀名为cfg的文件,我用的是STLink(-V2),所以起名为stlink.cfg,板子是stm32f407VET6,配置如下(稚晖君那有更详尽的配置)
同时要说明的是J-Link的配置有些特殊,因为它需要额外的操作。简单来说就是,OpenCD无法直接识别J-Link,需要把J-Link驱动改为串行接口,具体操作。实测过程中,只需到zadig改一下就好了(需要插拔),当然不同人情况可能不同。
source [find interface/stlink.cfg]
transport select hla_swd
# 内存大小 这里我的是512kb,就是80000
set WORKAREASIZE 0x80000
# chip name 改为相应型号
set CHIPNAME STM32F407VET6
source [find target/stm32f4x.cfg]
(或有不一样,但最值得关注的是下面这三句)
source [find interface/stlink.cfg]
transport select hla_swd
source [find target/stm32f4x.cfg]
最重要的是下面这句,无论是J-Link还是ST-Link,都必须有这一句,不然很容易出现连接不上的情况
transport select hla_swd
然后改掉之前那个我们随便选的cfg文件
把这个cfg替换成我们新建的那个stlink.cfg,同时把下载改为始终(这个看个人需要),点击确定
虽然工程的芯片型号我还没改,但是依旧可以“烧进去”的。烧成功后依然是令人心颤的红字,但是很明显跟之前不一样,有了诸如flash地址、Program Finshed等更为详尽的信息,且右下角会有已下载固件的提示
3,配置工程
点击上面那个.ioc文件,可以看到这个界面,再点击通过STM32CubeMX打开
等一下STM32CubeMX就会打开,默认是这个STM32F030F4PX芯片。这里肯定要改,点击红圈位置
选择你钟意的芯片,双击即可(我这里收藏过,你可以直接在左上方第一个搜索框内搜索)
为避免后续改名字的麻烦和文件残余的问题,更换芯片后就直接配置工程,在Toolchain/IDE一栏选择STM32CubeIDE(官方文档里说过),不用考虑什么SW4STM32
然后直接按Ctrl+S,它会给个框,按保存,再点击Yes,之后就会变成下面这个了
此时回到CLion,你会发现工程配置为正确的芯片了,但左边的包还没被清掉,还冒出个红字提示(emm,这个看情况吧,更严重一点的提示是找不到资源工程什么的,反正除了第一次要一下重启CLion也没有什么实际影响,后续再配置STM32CubeMX生成代码时,会自动加载)
不过打开资源管理器,就可以看到已经清理过了
现在可以放心地重启CLion了
重启后,确定即可,反正只有一个。不放心的话,再编译构建一下看看有没有正确的消息提示
如果你前面配置过cfg,这时cfg很可能被扬了,我是说最上面那个长得像爬虫的芯片,那个绿色标志没了
那么现在就可以体验一下配置cfg的整个流程了,点进编辑或是直接点击编辑配置,到里面点击左上角添加,然后找到那个OpenOCD下载并运行
然后,更改名称,添加可执行的二进制文件,添加面板配置文件( 点击选择路径下的第三个,可以直接到项目目录里),下载选择始终
4,配置芯片
接下来就可以通过点击目录里的ioc文件,回到STM32CubeMX,是一成不变的SYS、RCC和时钟树的配置,就不赘述了
这个外部晶振一定要对照一下自己手上这块板子的原理图,尽管芯片一样,但板子上的外部晶振就不一定了,有些是25MHz、8MHz,我这里是12MHz
设置好外部晶振频率并选择好通路后,直接在右侧输入想要达到的最终频率,它会自动配好
5,导出工程代码
Toolchain /IDE 要选STM32CubeIDE
到左侧的Code Generator里,勾选那个生成“.c/h”文件的,避免生成的代码乱七八糟的
生成后就可以点击close了,然后回到CLion,接下来可能弹出一个框让你选择配置文件,如果已经配置了,那么就直接跳过。没有的话,可以参考前面(2,检验初始化工程)的配置
6,接下来,开测!
接上仿真器,点击烧录,再点击旁边那只爬虫就可以得到一堆令人畏惧的红字,最上方会变成重新调试和停止,同时仿真器也会闪闪发光。
然后就可以设置断点什么的了,至于查看寄存器svd什么的其他博客里也有
嗯~,出乎意料,这不知道怎么回事就进去Error里了 囧 ,不过能调试能连接上单片机也算是莫大的幸福了,剩下的走一步看一步了
注释掉了之后就能正常运行了???原因未知,先记录一下,方便以后按Ctrl+F可以直接定位
详情请见目录【七、某些BUG】-【卡死在SystemClock_Config里的Error_Handler】
【锚点】:系统时钟配置的隐患
【半解决】:按一下板子上的重置键,或者使用OpenCD自带的重置MCU
【半解决+1】:在Open CD的配置文件中重置选项勾选运行
7,移植库或文件
这个是比较简单的,要么在CLion项目打开的情况下,直接该替换替换,该删删,之后修改CMakeLists。要么直接把原有的工程文件夹拖到关闭项目后的CLion里,点击一下信任,再把之前用STM32CubeMX新建工程时产生的CMakeLists和启动文件复制粘贴过来。总之,多试多查,不怕犯错,但前提是要把重要工程都备份好。下面内容建议先了解一下CMake构建的过程
①半移植
即在原有基础上修修补补改改,原目录如下
现在开始移植
如果此时你以无需使用STM32CubeMX,那么与其有关的文件可以删掉,注意.idea不建议删掉(但也无所谓,顶多弹个框按一下确定),两个ld文件不要删除,其余自便。Startup文件没有删,放到CORE里了
也就是说现在保留的部分是:两个ld文件和一个启动文件,以及idea文件夹
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++开发就要格外小心了,我在使用IAR时,在c文件对应的.h文件里直接调用C++的头文件是不会报错的(当然这样做并不好,IAR和CLion对头文件与源文件的映射似乎有些不同,IAR相较更为完善些),换到CLion就需要尽量避免C文件直接调用C++的头文件(虽然网上有解决方法)
测试过程中,使用含C++的小项目时,可以正常运行,而移植以前的稍大的项目时出现了下面错误,目前还在排查,发现是C函数以C++的方式编译,但无论是.h文件还是在cpp调用这个.h文件都加了extern "C",可还是出现这个报错。
查了很久很久,才找到这两篇博客undefined reference to ,undefined reference to `_sbrk‘,只是没有想到,我堂堂Windows系统下的嵌入式开发,使用的居然是Linux系统的消息提示,看来arm-none-eabi真是深藏不露
总而言之,就是arm-none-eabi默认是使用startfiles,且编译器为了节省空间,删减了一部分代码, 导致有些方法只有声明没有实现,需要自己实现。
这一部分的解决方法放在了下面目录的使用C++开发
且依这两篇博客所言,这个缺失的部分还是要补回来的,因为malloc、free、printf等难免会用到,具体解决方法在下面的目录【开发拓展-使用C++开发-找回编译器缺失的部分】
②完全移植
现以一个IAR工程为例
把整个文件夹移到一个纯天然目录(路径上不含任何中文、空格之类的),同时文件夹改成纯天然符合规范的名字
先删无关目录,如settings目录、Debug目录等。再删除原工程配置文件,如启动文件
再找到STM32CubeMX生成的配置文件,共6个,如下
复制到相应位置后,就可以在此工程右键打开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
②添加SVD
下载完后,为了方便添加以及后续可能出现的更换等等问题,于是在CLion的目录下新建了一个user_resources,里面专门用来添加各种包的。考虑到以后CLion可能会用于别的用途,那就再新建一个子目录MCU,在这里添加刚刚下载的SVD包,并解压
现在回到CLion,到相应调试窗口下,点击外设一栏,然后点击加载.svd文件,找到相应型号
选择第一个全都要或者挑选几个按需选
这里选择全都要,当然会冒出一堆外设,然后点击最外面的那个STM32F407.svd的,让它收起来,这样再展开时,所有外设都会默认收缩起来
2,开发改善
① 背景
打开所有设置或设置,然后【外观-背景图像】,选择想要的图片,确定即可
②去除特定警告
把光标放在报错位置,点击更多操作
点击编辑检查配置文件设置
取消勾选,确定即可
3,一些插件与调试方法
① 调试方法-博客
② 2023年Clion插件推荐
③ 自动格式化代码
Clang-Format用法详解,下面是博客里面的配置
#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(未完)
简单来说就是打印输出,没有打印输出的调试是不完整的
使用usart还需要一个USB转串口模块,很遗憾我手上这块只有一个仿真接口,连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++));
6,Git的使用方法
CLion中已经集成了Git,可以很方便的使用Git。具体操作可以参考一下下面的这个视频Git/Gitee可视化版本管理—使用CLion加速STM32Cube项目开发_哔哩哔哩_bilibili
7,打开汇编
右键代码,点击【显示程序集】,即可打开汇编代码
上方会出现一行命令,通过修改参数以查看不同情况下编译出的代码。比如,把-Og改为-O1,从调试级别变为了1级优化,再点击一下旁边的刷新 ,就可以看到一级优化下的汇编指令
六、开发拓展 ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚
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文件复制过来
如果编译报错,缺少什么什么__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)的
七、一天写不了几行BUG ( ´◔︎ ‸◔︎`)
有些由于忘了截屏,只能简单描述一下
1,文件在却显示No such file or directory
-> 这是因为文件冲突了,比如原先STM32F013F4PX的包没删,有两个main函数等;由STM32CubeMX生成代码时没有选STM32CubeIDE,使用默认的选择,生成包含所有库文件的代码,这就会有大量的重复
-》删除多余的库,再删除.idea文件夹和cmake-build-debug-mingw这样的编译文件。然后重新进入CLion,配置cmake什么一堆的,都是熟悉的内容
2,刚新建项目,CMake直接报出红字
或是出现no path什么的,那么就要好好看看设置里的工具链和CMake有没有配好,或是有没有下错版本。出现path很容易理解,可能配到了用户变量的Path,而非系统变量,也可能是没有重启
3,显示“不是有效的win32应用程序”
编译能过,但调试时显示运行 “'.elf' 时出错createprocess error=193, %1 不是有效的 win32 应用程序”之类的
-》这个之前没有使用C++时,重新调一下工具链和CMake就好了
-》(使用C++后?)没有配置OpenOCD也会出现,配置后就不会出现了,可以正常下载烧录
4,只有一个报错,并指向了蓝色的flash.id文件
进去一看有个提示,说什么【“ready”这个关键字只支持11.0以上的gcc,如果你有10.0的,请把它删除】。
-》按照本教程里的安装,下个13.2版本的arm-none-eabi-gcc,然后配置系统变量,加到工具链什么的
5,出现了完全摸不到头绪的提示
唯一能找到价值的还是标出的那一句
“ "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】,单从免费上来看,这就很明显了。
——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之类的文件即可
曾闻:硬件开发嘛,就是要反复折腾,折腾得多了,水平自然就提高了