CLion STM32 开发环境搭建
目录
- [CLion STM32 开发环境搭建](#CLion STM32 开发环境搭建)
- 
- [1. 开发环境概览](#1. 开发环境概览)
- [2. 环境搭建](#2. 环境搭建)
- 
- [1. 安装 CLion](#1. 安装 CLion)
- [2. 安装 STM32CubeMX](#2. 安装 STM32CubeMX)
- [3. 安装 STM32CubeCLT](#3. 安装 STM32CubeCLT)
- [4. 安装OpenOCD](#4. 安装OpenOCD)
 
- [3. CLion STM32环境配置](#3. CLion STM32环境配置)
- [4. 使用STM32CubeMX生成模板工程](#4. 使用STM32CubeMX生成模板工程)
- 
- [1. 配置要使用的外设](#1. 配置要使用的外设)
- [2. 配置时钟](#2. 配置时钟)
- [3. 配置工程](#3. 配置工程)
 
- [5. CLion 打开STM32工程](#5. CLion 打开STM32工程)
- 
- [1. 打开STM32CubeMX生成的工程](#1. 打开STM32CubeMX生成的工程)
- [2. 选择工具链,并编译](#2. 选择工具链,并编译)
- [3. 烧录 & 调试设置](#3. 烧录 & 调试设置)
 
- [6. printf 重定向输出](#6. printf 重定向输出)
- 
- [方法1. 只使用printf函数,重定向到串口](#方法1. 只使用printf函数,重定向到串口)
- [方法2. 完整的标准输入输出](#方法2. 完整的标准输入输出)
 
 
1. 开发环境概览
使用到的组件有:
| 组件 | 作用 | 推荐版本 | 
|---|---|---|
| CLion | C/C++ IDE,支持CMake、调试 | ≥2023.3 | 
| STM32CubeMX | STM32外设配置与代码生成 | ≥6.10 | 
| ARM GNU Toolchain | 编译工具链 (arm-none-eabi) | 13.3.rel1 | 
| OpenOCD | 调试与烧录工具 | ≥0.12.0 | 
| ST-Link / DAP驱动配置 | 连接STM32 | 最新版 | 
| CMake + Ninja | 构建系统 | CLion自带 | 
| STM32Cube HAL 库 | 外设驱动库 | 含于CubeMX中 | 
2. 环境搭建
1. 安装 CLion
下载地址:
👉 https://www.jetbrains.com/clion/
2. 安装 STM32CubeMX
下载地址:
👉 https://www.st.com/en/development-tools/stm32cubemx.html
作用:STM32 外设配置,用于生成 HAL 工程模板,输出工程类型选择 STM32CubeIDE 。
3. 安装 STM32CubeCLT
STM32CubeCLT集成了STM32开发过程中所需的一系列关键工具,下表概括了其主要组件和功能:
| 组件类别 | 工具示例 | 主要功能 | 
|---|---|---|
| 编译器 | arm-none-eabi-gcc(GNU ARM编译器) | 将C/C++代码编译为STM32可执行的二进制文件(如.elf、.bin)。 | 
| 调试器 | st-util(ST-Link工具) | 通过ST-Link与开发板通信,实现断点调试、变量监视等功能。 | 
| 烧录工具 | stm32programmercli(命令行版STM32 Programmer) | 将编译生成的固件下载到STM32芯片的Flash中。 | 
| 构建工具 | make、cmake、ninja | 解析项目的Makefile或CMakeLists.txt,自动化整个编译流程 | 
其目录如下:

其中已经包括了要使用到的 ARM 编译器 arm-none-eabi 和底层构建工具 Ninja。
4. 安装OpenOCD
下载地址:Download OpenOCD for Windows
是一个开源工具,允许使用各种下载器使用 GDB 调试各种 ARM 设备,用于支持通过ST-Link / DAP / JLink 调试器下载并调试芯片。
功能和特点:
- 与调试器(ST-Link / J-Link / CMSIS-DAP)通信
- 控制目标 MCU(复位、读写内存、烧录 Flash)
- 为 GDB 提供远程调试接口(localhost:3333)
- 支持众多调试探头:ST-Link, J-Link, CMSIS-DAP, FTDI芯片等。
- 支持海量目标芯片:几乎覆盖了所有主流的ARM Cortex芯片(STM32, GD32, Nordic, NXP等),还支持RISC-V, MIPS等其他架构。
3. CLion STM32环境配置
在 CLion 环境中,选择 设置 配置工具链

添加上OpenOCD 调试工具路径 :

4. 使用STM32CubeMX生成模板工程
打开 STM32CubeMX 选择芯片,这里使用stm32f103rct6为例子演示:
在STM32CubeMX中新建项目,选择芯片具体型号:STM32F103RCT6
1. 配置要使用的外设
⚠️ 这里别忘了使能调试接口,避免之后下载器没法识别设备。

2. 配置时钟

3. 配置工程
输出IDE 选择 STM32CubeIDE.


最后,点击生成代码,一个模板工程就创建好了。
5. CLion 打开STM32工程
1. 打开STM32CubeMX生成的工程
在CLion中打开工程目录,视图如下:

在选择配置文件时,先点击 跳过 。
2. 选择工具链,并编译
为项目选择上面设置的 工具链 ,并点击 build 图标对项目进行编译,可以看到编译成功。

3. 烧录 & 调试设置
在这里需要新建芯片配置文件,在文件根目录中新建一个配置文件,可以命名为 stm32f103rct6_config.cfg 文件命名不用有空格。

点击编译按钮旁边的配置栏下拉,选Edit Configurations,打开配置窗口:
选择上面新建的配置文件,并 确认 。

配置文件需要根据不同的调试器和芯片,创建对应的配置文件
如果使用的是自制DapLink作为调试器,文件的内容如下:
            
            
              text
              
              
            
          
          # choose st-link/j-link/dap-link etc.
adapter driver cmsis-dap
transport select swd
# 0x40000 = 256K Flash Size
set FLASH_SIZE 0x40000
source [find target/stm32f1x.cfg]
# download speed = 10MHz
adapter speed 10000如果是用ST-Link的话:
            
            
              text
              
              
            
          
          # choose st-link/j-link/dap-link etc.
source [find interface/stlink.cfg]
transport select hla_swd
source [find target/stm32f1x.cfg]
# download speed = 10MHz
adapter speed 10000前两行设置了仿真器的类型和接口,下面几行指定了Flash大小 、芯片类型 、下载速度等。
如果对自己的芯片不知道怎么设置,可以参考OpenOCD自带的一系列配置文件,路径在OpenOCD安装目录的share\openocd\scripts下:

target 目录下,可以看到很多支持的芯片型号,选择对应的配置文件即可。
只需要关注这几个目录:
- board:板卡配置,各种官方板卡
- interface:仿真器类型配置,比如ST-Link、CMSIS-DAP等都在里面
- target:芯片类型配置,STM32F1xx、STM32L0XX等等都在里面
设置好配置文件之后,就可以点击下载 或者调试按钮进行下载和在线调试了。
下载结果如下:
这里使用的时 CMSIS-DAP 进行下载和调试

调试界面如下:

在调试时,可以先将 STM32CubeCLT 中的 芯片对应的 SVD 文件复制到根目录下:

在调试时,添加对应的 .svd 外设文件:

6. printf 重定向输出
在Keil里面为了使用printf函数我们需要重定向fputc函数,如下:
            
            
              text
              
              
            
          
          int fputc (int ch, FILE *f)
{
    (void)HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
    return ch;
}其中的FILE定义在stdio.h头文件中,所以需要在项目中包含这个头文件。在Clion中环境中也是如此。在Clion中链接的是GNU-Tools-ARM-Embedded\arm-none-eabi\include里面的stdio.h,如果想使用标准输入输出函数功能,则需要重新改变标准输入输出的函数定义,使其重定向到串口,下面提供两种方法。
在 syscalls.c 源文件中,可以看到,下面两个函数就是标准输入输出的函数声明,只需要重新定义这两个函数,就可以实现标准输入输出。
            
            
              c
              
              
            
          
          /* Variables */
extern int __io_putchar(int ch) __attribute__((weak));  // 输出
extern int __io_getchar(void) __attribute__((weak));    // 输入方法1. 只使用printf函数,重定向到串口
在源文件中添加如下宏定义
            
            
              c
              
              
            
          
          #ifdef __GNUC__                  //串口重定向
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif再定义 __io_putchar(int ch) 函数:
            
            
              c
              
              
            
          
          PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}之后,引入头文件 #include <stdio.h> 就可以重定向printf 到串口uart1了。
⚠️ 注意,方法1中,printf 函数是默认使用行缓冲 ,需要遇到 \n 回车字符时,才会输出。
在使用printf时,需要在后面加上 \n
            
            
              c
              
              
            
          
          printf("hello world!\n");
printf("%0.2f \n",f);方法2. 完整的标准输入输出
新建一个retarget.h文件内容如下:
            
            
              c
              
              
            
          
          #ifndef _RETARGET_H__
#define _RETARGET_H__
#include "stm32f1xx_hal.h"
#include <sys/stat.h>
#include <stdio.h>
void RetargetInit(UART_HandleTypeDef *huart);
int _isatty(int fd);
int _write(int fd, char *ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char *ptr, int len);
int _fstat(int fd, struct stat *st);
#endif //#ifndef _RETARGET_H__retarget.c 文件定义如下:
            
            
              c
              
              
            
          
          #include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <stdint.h>
#include "retarget.h"
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
UART_HandleTypeDef *gHuart;
void RetargetInit(UART_HandleTypeDef *huart)
{
    gHuart = huart;
    /* 关闭标准输出 stdout 的缓冲机制
     * ,使 printf 等输出函数的内容 立即输出到终端,
     * 而不是等缓冲区满或遇到换行符才输出。. */
    setvbuf(stdout, NULL, _IONBF, 0);
}
int _isatty(int fd)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
        return 1;
    errno = EBADF;
    return 0;
}
int _write(int fd, char *ptr, int len)
{
    HAL_StatusTypeDef hstatus;
    if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
    {
        hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
        if (hstatus == HAL_OK)
            return len;
        else
            return EIO;
    }
    errno = EBADF;
    return -1;
}
int _close(int fd)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
        return 0;
    errno = EBADF;
    return -1;
}
int _lseek(int fd, int ptr, int dir)
{
    (void) fd;
    (void) ptr;
    (void) dir;
    errno = EBADF;
    return -1;
}
int _read(int fd, char *ptr, int len)
{
    HAL_StatusTypeDef hstatus;
    if (fd == STDIN_FILENO)
    {
        hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
        if (hstatus == HAL_OK)
            return 1;
        else
            return EIO;
    }
    errno = EBADF;
    return -1;
}
int _fstat(int fd, struct stat *st)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    {
        st->st_mode = S_IFCHR;
        return 0;
    }
    errno = EBADF;
    return 0;
}
#endif //#if !defined(OS_USE_SEMIHOSTING)在CMakeLists.txt 文件中,添加这两个文件到工程,更新CMake,编译之后会发现,有几个系统函数重复定义了,被重复定义的函数位于Src目录的syscalls.c文件中,我们把里面重复的几个函数删掉即可。
这里在项目根目录下,新建了一个 target 目录,并把源文件放在改目录下:
在CMakeLists.txt中添加文件,如下:

在main函数的初始化代码中添加对头文件的引用并注册重定向的串口号:
            
            
              c
              
              
            
          
          #include "retarget.h"
RetargetInit(&huart1);示例:
在main函数中调用 RetargetInit(&huart1);
            
            
              c
              
              
            
          
            /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  RetargetInit(&huart1);
  printf("hello world\n");
  float x = 122.44f;
  printf("float x = %0.2f",x);
  /* USER CODE END 2 */输出结果:
            
            
              c
              
              
            
          
          hello world
float x = 122.44结果正确,浮点数也可以打印。
备注:
如果上面的修改完成之后可能会发现无法正常读取浮点数,这里修改CMakeList.txt,加入下述编译选项
            
            
              text
              
              
            
          
          set(COMMON_FLAGS "-specs=nosys.specs -specs=nano.specs -u _printf_float -u _scanf_float")