文章目录
前言
我们自己用clion进行开发,但其他人的项目用的是keil,我们怎么跟他们一起开发,怎么打开他们的工程并顺利在clion里运行,编辑呢? 我的方法是复制粘贴一下cubeMX生成的cmake目录(稍加改动)和 两个.ld链接文件到keil目录下以直接用cmake管理keil的目录。 同时给出了关于一些printf重定向的简易解决办法: keil提供了一些c标准库底层实现导致的printf区别——复制粘贴一个syscalls.c文件并稍加改动以解决。
一、clion配置前提
clion需要的配置:【如果没配置好可以搜如何优雅使用clion进行嵌入式开发教程,一步一步配置写的很详细】
- openOCD下载器(注意用stlink.cfg/daplink.cfg等写好自己使用的下载器配置,具体写法可以搜教程)
- cubeMX(用来获取对应板子的.ld文件,cmakelists.txt)
二、操作过程
1.对比目录差异
- 这是keil的工程文件夹:
- 这是由cubeMX生成的clion文件夹(其实是stm32cubeIDE的文件夹)
可以看到,目录管理方式还是不太一样的,但总体类似。
总结:
keil: hal库在Libraries里, .c/.h在User里 启动文件.s也放在Libraries里
clion:hal库在Drivers里, .c/.h在Core里 启动文件在Core里专门的Startup文件夹
2.将Clion需要的文件移到Keil目录下
这些文件可以用cubeMX新建一个和keil里芯片对应的空的工程模板里获得
需要移动的有:
- CMakeLists.txt: 这个是由cubeMX为Clion自动写好的东西,能很好地管理文件夹,指定了项目名称(需更改),目录结构(需更改)
- 两个.ld链接器文件(STM32H743IITX_FLASH.ld)(STM32H743IITX_RAM.ld):这两个文件规定了对应芯片的内存布局,clion需要使用这个来将代码写到单片机上。同一块芯片,.ld文件是一样的,可以直接移动。
- .s启动文件startup_stm32h743iitx.s:由于两个IDE启动文件放的地方不同,最好移动到User里
实际操作:
1. 从cubemx为clion创建的项目模板中,复制需要移动的文件到keil目录下
- CMakeLists.txt 与 两个.ld链接器文件:
从:
到:
启动文件:
从:
到:
2. 用clion打开keil的工程(可以直接用clion打开CMakeLists.txt)
可以看到,cmake-build-debug文件夹已经创建出来了,但是没法编译,还要改一些配置:
3. 点开CMakeLists.txt,更改以下部分:
-
把这个改成项目文件夹的名字。原先的是项目模板的文件夹名。很重要,因为CMAKE路径都是根据这个文件夹名设置的
-
将驱动库路径设为KEIL的命名方式
原先:
改为:
这句话是把驱动库的.h文件和我们代码的.h库文件都包含进来。如果是其他更复杂的目录,可以GPT一下看看怎么改。 -
将代码文件目录改一下
原先:
改为:
这个file(GLOB_RESCURSE SOURCES XX)会查找并包括两个目录下所有文件。
ps:所以我们写程序,在User里又加了个专门的外设文件夹,放对应的.c.h,也都没问题。但需要注意,每次添加文件夹,都需要选择“重新加载cmake项目”选项,不然还是找不到。 尽管如此,自己加文件夹的话,#include “…/” 这样的相对路径还是要写的 -
忽略Libraries里的startup.s文件
在CMakeLists.txt里,file(GLOB_RECURSE)那句下面一个地方添加以下代码,进而忽略掉Keil带的启动文件,转而用我们添加进去的启动文件(不然会报错,keil的启动文件有的放在template里做模板用,clion也会调用)
foreach(_file ${SOURCES})
if((_file MATCHES "arm") OR (_file MATCHES "gcc"))
list(REMOVE_ITEM SOURCES ${_file})
endif()
endforeach()
4. 从新加载cmake项目,即配置成功啦!
3. (如果需要printf重定向) c库的一些操作
keil默认使用的是ac5或ac6(arm complier 6)编译器,而且可以选择是否使用MicroLib选项,即可以使用专门为嵌入式系统开发的c库(不是普通的stdio.h)。
然而我们在clion中配置了自己下载的gcc编译器,因此如果keil的工程需要使用printf()等函数的话,clion这边不能直接调用微库,就会出现printf(),scanf(),getchar()报错等信息,显示缺失各种定义的问题。
解决办法:clion+cubemx有自己的解决办法,是包含一个叫“syscalls.c”的文件。这个文件就是实现了各种重定向,提供了c库的各种底层实现。所以我们在cubemx为clion生成的各种项目里总能找到这个文件,但在keil的项目里是找不到的。
因此,直接从其他cubemx生成的项目里随便找一个syscalls.c的文件,移到clion项目源文件目录下,也不需要在其他地方加什么include (保证能被cmake 的 source找到,添加进编译就好了(从新加载一下cmake项目)),然后之前的报错就突然少了很多
但还是剩下一个报错,说缺少_sbrk的定义
于是我把这段代码加入了syscalls.c的前面,同时在syscalls.c里include一下“cmsis_arm.h”(不然没有__get_MSP宏)报错也就消失了
extern char _end;
static char *heap_end = &_end;
caddr_t _sbrk(int incr) {
char *prev_heap_end = heap_end;
char *next_heap_end = heap_end + incr;
/* Check for stack collision */
if (next_heap_end <= (char *)__get_MSP()) {
heap_end = next_heap_end;
return (caddr_t)prev_heap_end;
} else {
return (caddr_t)-1;
}
}
在syscalls.c文件里加入一行头文件(不同版本syscalls.c好像不一样,没有这个报错可以跳过)
然后,在编译就没有各种报错了,接下来进行重定向环节(也就是用HAL_UART_Transmit()函数替换原来printf()调用的输出函数)
这里采用官方给的例子(给STM32-EVAL开发板写的官方示例文件,资料可以在st官网找到描述)的重定向写法。
宏定义:
宏函数体:
代码抄一遍:
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/*Place your implementation of fputc here
e.g. write a character to the USART1 and Loop until the end of transmission */
HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
这些放在main.c函数里面随便找地方写就好。首先检查__GNUC__是否定义,即检查是否使用的是Clion的GCC编译器。如果是的话,就定义一个宏,也就是改变 int __io_putchar(int ch),否则的话改变fputc(keil 中改变fputc,这个否则指的就是keil那些用的非GCC编译器)
把这两段代码加在main.c里面,就已经可以正常使用printf();了。那么改变的这两个宏指向哪里呢?点进去看到,__io_putchar()在syscalls.c里被定义,在_write函数内被调用。所以说,GCC中,printf调用了_write,而其他编译器,printf调用fputc.我们是把_write里的__io_putchar()改成uart的传输函数,从而实现的重定向。
总结
再配个openOCD就可以下载了。
第一次写博客,纯萌新 虽然把程序能下载了,但对cmake的构建方式等等还是不了解,这么搞或许步骤有点太复杂了,南辕北辙了也说不定…
嘛,总之祝大家学习开心喵!