
最近正在学习STM32,由于我是小白水平,并且已经习惯了在Clion使用vim插件,所以在Keil里面写代码实在是不习惯,才开始在网上找前辈的文章配置Clion。本文参考和引用了以下前辈的文件 ,在此致谢!本文只作功能实现,不讲原理,因为我也不会。
1、知乎:【教程】手把手教你用Clion进行STM32开发【如何优雅の进行嵌入式开发】
3、在CLion中进行STM32F1系列标准库开发,解决printf问题并添加CMSIS-DSP库
1、Clion安装
网上教程很多,请自行安装。
2、下载安装STM32CubeMX
STM32CubeMX6.11.0不能打开升级6.5.0版本的项目。请移步官网自动下载安装,https://www.st.com/en/development-tools/stm32cubemx.html
3、下载OpenOCD
OpenOCD是用于对STM32进行下载仿真的开源软件包,我使用的版本是20231002,将文件夹解压放在无空格、无中文的路径下。
下载地址:https://gnutoolchains.com/arm-eabi/openocd/。
4、链接OpenOCD和STM32CubeMX
在Clion软件中操作FIle-->Setting-->Build,Execution,Deployment

单击后面的Test按钮,报绿色说明运行正常.
4、安装gcc-arm-none-eabi
选择"Windows 32-bit ZIP package"格式下载。文件夹解压放在无空格、无中文的路径下。
下载地址:https://developer.arm.com/downloads/-/gnu-rm,
添加系统环境变量:

在CMD窗口中输入arm-none-eabi-gcc -v,有返回信息说明安装成功。

5、安装MinGW(可以不安装)
最新版的Clion内已经内置了MinGW环境,也可以手动设置。
下载地址:https://sourceforge.net/projects/mingw-w64/files/,选择图示版本:

文件夹解压放在无空格、无中文的路径下。将WinGW的bin文件夹添加至系统环境变量。在CMD窗口中输入gcc -v,有返回信息说明安装成功。

6、配置Clion
File-->Build,Execution,Deployment-->Toolchain
如果有多个编译配置,记得将准备使用的配置移至最上部。

外部MinGW

内置MinGW

外部Build Tool

外部Debugger
Build Tool:选择内置“Detected:ninja.exe”网上说会编译速度会比mingw32-make快,未测试。还有一个区别是:

ninja

mingw32-make
虽然使用ninja未在窗口中输出显示hex和bin文件,但是在项目文件夹的依然会生成这两个文件。
Debugger:选择内置“Bundled GDB”网上说可以使用Clion的调试功能,暂时未测试。
三、创建STM32CubeMX代码
1、新建文件夹STM32_Stdlib_0605_0329
2、新建项目

第一次启动STM32CubeMX时,有可能会失败,没有IOC文件,此时需重新创建项目。
创建项目成功后,会弹出“Select Board Config File”,选“Skip”。
3、打开STM32CubeMX

4、选择MCU型号

双击图示位置,在弹出的新窗口中选择MCU型号;

双击确认
5、项目管理

Project Name:输入新创建的文件夹;
Toolchain/IDE:
STM32CubeMX6.5.0-->SW4STM32
STM32CubeMX6.11.0-->STM32CubeIDE
Minimum Heap/Stack Size:默认够用,对应Keil启动文件内的Stack_Size和Heap_Size。

分别放置头文件和源文件
单击右上角的“GENERATE CODE”创建代码。

重写IOC文件

Yes

Close
四、修改标准库文件
1、下载正点原子模板源码
https://pan.baidu.com/s/1qZgG-UAznhQWgGcX8yFduQ?pwd=zz5e
下载“A盘”-->4、程序源码-->2、标准例程-库函数版本.zip,解压缩得到“实验0-1 Template工程模板-新建工程章节使用”文件夹备用。
2、添加文件
将STM32_Stdlib_0605_0329中STM32CubeMX生成的“Core/Src/syscalls.c”文件另存为UTF-8编码格式,备用。
STM32CubeMX 6.5.0:
(1)、将“Core/Startup”文件夹放到STM32_Stdlib_0605_0329目录下,删除“Core”目录。
(2)、将“Drivers”目录删除;
(2)、把Template工程模板的“CORE”、“STM32F10x_FWLib”、“SYSTEM”和“USER”四个目录复制到STM32_Stdlib_0605_0329目录下。
(3)、“STM32F10x_FWLib”文件夹不修改;
(4)、将无用文件删除,将头文件和源文件分别放置。

STM32Cube 6.11.0:
(5)、其余3个文件夹将无用文件删除,将头文件和源文件分别放置。

3、修改CMakeLists.txt文件
STM32CubeMX 6.5.0修改内容:
//第52行修改为以下内容
include_directories(Core/Inc SYSTEM/Inc STM32F10x_FWLib/Inc USER/Inc)
//第56行修改为以下内容
file(GLOB_RECURSE SOURCES "Startup/*.*" "CORE/*.*" "SYSTEM/*.*" "STM32F10x_FWLib/*.*" "USER/*.*") STM32CubeMX 6.11.0修改内容:
//第52行修改为以下内容
include_directories(Core/Inc SYSTEM/Inc STM32F10x_FWLib/Inc USER/Inc)
//第56行修改为以下内容
file(GLOB_RECURSE SOURCES "Core/*.*" "SYSTEM/*.*" "STM32F10x_FWLib/*.*" "USER/*.*")
这一步很重要,每次修改都要更新。

Load CMake Changes
4、修改STM32F103RETX_FLASH.ld文件(6.5.0无需此步)
STM32CubeMx 6.11.0 生成的 .ld 文件中含有 READONLY 关键字,此关键字只能在 gcc 11 版本及以后使用,gcc 10及以下版本解析不了报错(在后面生成的注释中也有说明),所以需要将.ld 文件,删除所有 “(READONLY) ”字段删除。
援引的文章:https://www.cnblogs.com/XuYuFan/p/18094079
5、修改USER/Inc/stm32f10x.h文件
#ifndef __STM32F10x_H
#define __STM32F10x_H
//在原文件第52行增加以下内容
//#define STM32F10X_LD //16KB≤FLASH≤32KB
//#define STM32F10X_MD //64KB≤FLASH≤128KB
#define STM32F10X_HD //256KB≤FLASH≤512KB
#define USE_STDPERIPH_DRIVER
//以上为增加的内容
#ifdef __cplusplus
extern 6、修改CORE/Src/core_cm3.c文件

7、将SYSTEM/Src/sys.c文件修改为以下内容

8、编译工程
如果编译按钮是灰色的,可清除工程缓存。

五、使用ST-Link下载程序
在STM32_Stdlib_0605_0329目录下新建ST_Link.cfg文件
# choose st-link/j-link/dap-link etc.\
# adapter driver cmsis-dap
# board:板卡配置,各种官方板卡
# 路径在OpenOCD安装目录的share\openocd\scripts下:
# interface:仿真器类型配置,比如ST-Link、CMSIS-DAP等都在里面
# target:芯片类型配置,STM32F1xx、STM32L0XX等等都在里面
# 设置好配置文件之后,就可以点击下载或者调试按钮进行下载和在线调试了。
# 在配置文件中不要加reset_config srst_only这一句,会导致下载失败,这一句是指示系统重启的,删除不影响下载。
source [find interface/stlink.cfg]
transport select hla_swd
# 0x10000 = 64K Flash Size
# 0x100000 = 1024k Flash Size
# set FLASH_SIZE 0x100000
source [find target/stm32f1x.cfg]
# download speed = 10MHz
adapter speed 10000000
//实物测试通过 单击右上角编译图标右侧的下三角,选择“Edit Configurations”

单击左上角“+”新建配置,选择“OpenOCD Download & Run”

选择板配置文件
单击Clion右上角的运行图标下载程序。
六、串口输入输出的实现
1、在"SYSTEM/Inc"下新建“retarget.h”文件。
#ifndef _STDUSART_RETARGET_H__
#define _STDUSART_RETARGET_H__
#include <stdio.h>
#include "stm32f10x_usart.h"
#ifdef __GNUC__
/* With GCC, 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__ */
#ifdef __GNUC__
#define GETCHAR_PROTOTYPE int __io_getchar (void)
#else
#define GETCHAR_PROTOTYPE int fgetc(FILE * f)
#endif /* __GNUC__ */
/**
* @brief 注册重定向串口
* @param usartx 需要重定向输入输出的串口
*/
void RetargetInit(USART_TypeDef *huart);
void USART1_Config(unsigned int bound);
#endif //_STDUSART_RETARGET_H__
2、在“SYSTEM/Src”下新建“retarget.c”文件。
#include "retarget.h"
/* 定义USART端口,用于注册重定向的串口(也可以用UART,根据实际情况来改写) */
static USART_TypeDef *sg_retargetUsart;
/**
* @brief 注册重定向串口
* @param usartx 需要重定向输入输出的串口
*/
void RetargetInit(USART_TypeDef *usartx) {
/* 注册串口 */
sg_retargetUsart = usartx;
/* Disable I/O buffering for STDOUT stream, so that
* chars are sent out as soon as they are printed. */
setvbuf(stdout, NULL, _IONBF, 0);
/* Disable I/O buffering for STDIN stream, so that
* chars are received in as soon as they are scanned. */
setvbuf(stdin, NULL, _IONBF, 0);
}
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE {
/* 发送一个字节数据到串口 */
/* 很简单,直接调用库函数中的 串口发送数据函数 */
USART_SendData(sg_retargetUsart, (uint8_t) ch);
/*等待发送完毕*/
while (USART_GetFlagStatus(sg_retargetUsart, USART_FLAG_TXE) == RESET);
/* 返回发送的字符 */
return ch;
}
/**
* @brief Retargets the C library scanf, getchar function to the USART.
* @param None
* @retval None
*/
GETCHAR_PROTOTYPE {
/* 很简单,直接调用库函数中的接收 */
/* 等待串口输入数据 */
while (USART_GetFlagStatus(sg_retargetUsart, USART_FLAG_RXNE) == RESET);
/* 直接返回接收到的字符 */
return (int) USART_ReceiveData(sg_retargetUsart);
}
void USART1_Config(unsigned int bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/*使能端口时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
/*USART1-TX输出脚配置*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*USART1-RX接收脚配置*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*串口参数配置*/
USART_InitStructure.USART_BaudRate = bound;//设置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//一帧中传输或者接收的数据位数(8位/9位)
USART_InitStructure.USART_StopBits = USART_StopBits_1;//定义发送的停止位个数
USART_InitStructure.USART_Parity = USART_Parity_No ;//设置为不需要校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用流控制模式
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//发送且接收使能
USART_Init(USART1, &USART_InitStructure);
/*使能接收终端*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
/*配置好参数之后,使能USART1外设*/
USART_Cmd(USART1, ENABLE);
/*USART1终端优先级的配置*/
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/*设置优先级分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//指定IRQ通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定先占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//从优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //定义的IRQ是被使能还是失能
NVIC_Init(&NVIC_InitStructure);
}
/*重定向c库函数printf到USART1,调用printf会将需要打印的数据从串口发送出去*/
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (unsigned char) ch);
while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET); //等到USART1发送完成
return (ch);
}
/*串口发送一个字符*/
void USART_SendChar(USART_TypeDef* pUSARTx, uint8_t c)
{
USART_SendData(pUSARTx, c);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/*
*串口发送一个字符串*/
void USART_SendString(USART_TypeDef* pUSARTx, char* str)
{
uint32_t n = 0;
while (*(str + n) != '\0')
{
USART_SendChar(pUSARTx, *(str + n));
n++;
}
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/*
int main(void)
{
USART1_Config(9600);
NVIC_Configuration();
printf(“********************************”);
printf(“hello ward!\r\n”);
printf(“********************************”);
While(1){} //阻止程序结束
}
*/
3、将“SYSTEM/Src/usart.c”中的fputc函数段注释掉。
4、将第四章第二步中备用的文件“syscalls.c”文件放到“CORE/Src”文件夹中。
现在就可以使用getchar,scanf,printf函数了。
七、修改项目生成目标名称
1、修改CMakeLists.txt文件第18行
project(Target_Name C CXX ASM)
2、重新CMake

如果没有CMake选择,可清除工程缓存。
3、重新编译文件,Build-->Build Project。
此时右上角的编译按钮仍是不可用,修改“Run/Debug Configurations”中的“Target”,选择新生成的elf文件即可正常。