【STM32】CLion STM32开发环境搭建

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中。
构建工具 makecmakeninja 解析项目的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")
相关推荐
咚璟7 小时前
TTL转485电路
单片机·嵌入式硬件
东木君_8 小时前
芯外拾遗第二篇:编译、工具链、烧录,你真的搞懂了吗?
linux·单片机·操作系统·嵌入式
laocooon5238578869 小时前
运行当前位置,显示文件全名,检查是否扩展名多次重叠
stm32·单片机·嵌入式硬件
沉醉不知归路19 小时前
cursor导入keil工程详细步骤
stm32
D.....l10 小时前
STM32学习(MCU控制)(I2C 模拟)
stm32·单片机·学习
国科安芯10 小时前
基于ASM1042通信接口芯片的两轮车充电机性能优化研究
服务器·网络·人工智能·单片机·嵌入式硬件·性能优化
A9better11 小时前
嵌入式开发学习日志42——stm32之SPI工作方式
stm32·单片机·嵌入式硬件·学习
qqxhb12 小时前
系统架构设计师备考第60天——嵌入式硬件体系&软件架构
单片机·嵌入式硬件·系统架构·存储器·处理器·可裁剪·强实时