配置CLion用于TI MSPM0开发【丝滑的嵌入式开发】
相信打过电赛的同学一定饱受TI MSPM0G3507的折磨,Keil这种上世纪风格的IDE(不只是UI),可以说几乎没有体验感,而TI自家的CCS,和STM32CubeIDE可以说是半斤八两,毕竟这俩都是从Eclipse魔改过来的,和keil相比也没多多少智能化设计,体验感上也只是从依托换到另依托。而CLion作为JetBrains家族的IDE,专精C/C++开发,可以说是非常好的选择,近几年也对
配置CLion用于TI MSPM0开发【丝滑的嵌入式开发】
专为TI MSPM0G3507配置的CLion开发模板,集成FreeRTOS实时操作系统和图形化配置工具。
写在前面
相信打过电赛的同学一定饱受TI MSPM0G3507的折磨,Keil这种上世纪风格的IDE(不只是UI),可以说几乎没有体验感,而TI自家的CCS,和STM32CubeIDE可以说是半斤八两,毕竟这俩都是从Eclipse魔改过来的,和keil相比也没多多少智能化设计,体验感上也只是从依托换到另依托。
而CLion作为JetBrains家族的IDE,专精C/C++开发,可以说是非常好的选择,近几年也对STM32开发做了很多适配,也收获了很多好评,强大的代码补全、界面风格、各种插件、流畅性等众多优点吸引了一大批用户,本文为TI MSPM0G3507配置了一套CLion开发的工程模板。
核心功能
- 完整的CLion工程配置
- 预移植FreeRTOS
- 集成TI SysConfig图形化外设配置
- OpenOCD程序下载与调试
说明一下,目前只适配了XDS110调试器,openOCD在原生daplink上下载MSPM0经常锁死,所以目前XDS110是最稳妥的方案。
🛠 硬件要求
开发硬件
- MSPM0G3507开发板
- XDS110调试器
软件环境
- CLion 2023.2或更新版本
- arm-none-eabi-gcc
- TI SysConfig
- OpenOCD(注意,MSPM0G3507需要下载特定的open OCD版本,请参考这里)
🚀 快速开始
克隆仓库
git clone --recursive git clone https://gitee.com/xulijun_2003/MSPM0.git
仓库链接也放这里:
github:https://github.com/Xulijun180/Clion_MSPM0_Template
gitee:https://gitee.com/xulijun_2003/MSPM0
环境配置
下载arm-none-eabi-gcc:
解压到一个文件夹,并把安装目录下的bin文件夹添加到环境变量:

然后重启使得环境变量生效之后可以在命令行里用以下语句测试:
arm-none-eabi-gcc -v
如果有信息输出,那就是装好了。
openOCD配置
OpenOCD是用于对芯片进行下载仿真的工具,是一个开源软件包,但是注意,MSPM0G3507需要下载特定的open OCD版本,请参考这里,下载好解压到一个目录就行,后面会在Clion中链接这个目录
在Clion中链接openOCD的方法:
打开File-Settings-Build,Execution,Deployment选项卡,在Toolchains下面添加一个MinGW环境:C编译器和C++编译器填入相应的地址,配置完成后如图所示

进入Cmeke设置确认一下,选择工具链为配置好的STM32工具链

SYSCONFIG配置
- 个人感觉sysconfig是TI开发工具链里体验感还算可以的软件,研究了一下在Clion中如何集成。
- 首先,打开sysconfig安装目录,找到这个sysconfig_gui.bat,并记住这个文件所在的目录。

- 打开Clion->设置->工具->外部工具,点击+号,添加一个外部工具,名称随便起,我这里就叫sysconfig,程序路径填写刚才记下的目录,注意这里的实参填
$FileDir$\empty.syscfg,工作目录也如图填就可以,根据你安装的sysconfig目录自己做修改。设置完后点击确定即可。

- 然后,在Clion中打开工程,在文件目录中找到
empty.syscfg,右键该文件,选择使用外部工具sysconfig。

- 正常的话就可以看到当前工程的sysconfig正常打开了,对工程的配置跟keil和CCS没有区别。

- 配置完后点击保存,回到工程就可以看到文件已经更新。
编译及下载
前面的步骤都完成后,用Clion打开仓库提供的例程
在IDE底部的Cmake选项卡中如果没有提示错误,说明没有配置问题了
点击这个按钮可以更新工程Cmake。
顶部的这三个按钮分别是编译、下载和调试
点击编译可以看到编译输出:
可以看到输出了用于烧录的hex、bin以及elf文件
烧录程序以及在线调试
Clion烧录程序之前通用需要进行一些设置。
点击编译按钮旁边的配置栏下拉,选Edit Configurations,打开配置窗口:
在这个位置填入配置文件的路径,配置文件在工程目录里有提供,或者你也可以自己新建一个,内容如下:
source [find interface/xds110.cfg]
transport select swd
source [find target/ti_mspm0.cfg]
adapter speed 10000
设置好配置文件之后,就可以点击下载或者调试按钮进行下载和在线调试了。
Clion支持全功能的断点调试,还可以在代码里直接观察变量的值,体验感还是非常顺畅的
另外,添加相应的svd文件后,还可以在窗口内直接观察寄存器的值,svd文件我也放在工程目录里了
另外Clion对FreeRTOS的调试功能也有支持,可以在调试页面可以查看任务信息等

总结
- 其实总体和Clion开发STM32没有太大的区别,只是集成了sysconfig,另外对FreeRTOS的调试功能做了适配,可以方便的查看任务信息等。
如果遇到任何CMake相关的报错,一般是由于缓存没有更新引起的,可以在CLion中选Tools-CMake-Reset Cache and Reload Project即可解决。
printf重定向问题
在Keil里面为了使用printf函数我们需要重定向fputc函数:
int fputc(int ch, FILE *stream)
{
while (DL_UART_isBusy(UART0_INST) == true);
DL_UART_Main_transmitData(UART0_INST, ch);
return ch;
}
其中的FILE定义在stdio.h头文件中,所以需要在项目中包含这个头文件,但是经过测试发现,Keil里面包含的是MDK\ARM\ARMCC\include这个目录下的stdio.h,而在Clion中是不会链接到这个文件的。因此如果在Clion中也按之前的方法进行重定向,会发现printf没有任何输出。
在Clion中链接的是GNU-Tools-ARM-Embedded\arm-none-eabi\include里面的stdio.h,如果仍然想使用printf函数功能,则需要进行如下操作:
新建一个retarget.h文件内容如下:
#ifndef _RETARGET_H__
#define _RETARGET_H__
#include "ti_msp_dl_config.h"
#include <sys/stat.h>
#include <stdio.h>
void RetargetInit(UART_Regs *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文件内容如下:
#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <retarget.h>
#include <stdint.h>
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
UART_Regs* gHuart;
void RetargetInit(UART_Regs* huart)
{
gHuart = huart;
/* Disable I/O buffering for STDOUT stream, so that
* chars are sent out as soon as they are printed. */
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)
{
if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
{
for (int i = 0; i < len; i++)
{
// 阻塞发送一个字节
DL_UART_Main_transmitData(gHuart, (uint8_t)ptr[i]);
// 如果硬件需要等待发送完成,这里可能要加等待
while (DL_UART_isBusy(gHuart) == true);
}
return len;
}
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)
{
if (fd == STDIN_FILENO)
{
DL_UART_Main_receiveData(gHuart);
return 1;
}
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)
再新建一个syscall.c如下:
/**
*****************************************************************************
**
** File : syscalls.c
**
** Author : Auto-generated by System workbench for STM32
**
** Abstract : System Workbench Minimal System calls file
**
** For more information about which c-functions
** need which of these lowlevel functions
** please consult the Newlib libc-manual
**
** Target : STMicroelectronics STM32
**
** Distribution: The file is distributed ��as is,�� without any warranty
** of any kind.
**
*****************************************************************************
** @attention
**
** <h2><center>© COPYRIGHT(c) 2019 STMicroelectronics</center></h2>
**
** Redistribution and use in source and binary forms, with or without modification,
** are permitted provided that the following conditions are met:
** 1. Redistributions of source code must retain the above copyright notice,
** this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright notice,
** this list of conditions and the following disclaimer in the documentation
** and/or other materials provided with the distribution.
** 3. Neither the name of STMicroelectronics nor the names of its contributors
** may be used to endorse or promote products derived from this software
** without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*****************************************************************************
*/
/* 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 */
//#undef errno
extern int errno;
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));
register char * stack_ptr asm("sp");
char *__env[1] = { 0 };
char **environ = __env;
/* Functions */
void initialise_monitor_handles()
{
}
int _getpid(void)
{
return 1;
}
int _kill(int pid, int 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)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
*ptr++ = __io_getchar();
}
return len;
}
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}
int _unlink(char *name)
{
errno = ENOENT;
return -1;
}
int _times(struct tms *buf)
{
return -1;
}
int _stat(char *file, struct stat *st)
{
st->st_mode = S_IFCHR;
return 0;
}
int _link(char *old, char *new)
{
errno = EMLINK;
return -1;
}
int _fork(void)
{
errno = EAGAIN;
return -1;
}
int _execve(char *name, char **argv, char **env)
{
errno = ENOMEM;
return -1;
}
在main函数的初始化代码中添加对头文件的引用并注册重定向的串口号:
#include "retarget.h"
RetargetInit(UART_0_INST);
...
然后就可以正常使用printf了
后续我会将这几个文件直接到仓库里,可以打开即用
注意
- 区别于此前网上流传的Clion开发STM32环境,本模板为个人移植,没有官方支持,可能存在未知的问题,请谨慎使用,当然也欢迎大家提issue或者PR指出并且一起完善这套模板
更多推荐

所有评论(0)