windows下使用CLion+OpenOCD+expect做嵌入式开发(在CLion中编译、烧录、调试stm32)

简介

实现了在CLion中编译、烧录、调试stm32。
主要参考了 在Mac下使用CLion做嵌入式开发CLion for embedded development
步骤简要是:建立Eclipse项目并转为CLion项目->在CLion中编译->在CLion中调用OpenOCD和Tcl expect脚本直接烧写程序->CLion gdb窗口中调试。
步骤操作比较多,也可直接安装配置环境后直接用我github上的项目pro_tmpl_stm32f10x,基于stm32f10x的,若其它芯片则可用Eclipse生成固件库然后代替项目中system文件夹并修改CMakelist编译参数就行。
若在linux或mac系统下用Clion,配置应该差不多,就expect脚本里声音提示函数部分要修改一下。
以下所有代码块推荐在github我的项目里拷贝,因为github里代码可能有所更新

PS:本人是刚入门STM32的菜鸡,由于之前写java和php、c++等用了Jetbrains系列的IDE后在其它方面用其它开发工具感觉体验差不少,于是打什么代码都想用Jetbrains系列的IDEs,比如去年的汇编就用CLion打(有时间另开篇写设置汇编代码高亮和代码提示等的相关方法)。由上述所以刚学STM32就不用MDK不用Keil,直接用CLion和Eclipse开搞了。

先放张CLion里写stm32的工作图:


更新进展

  • 2017年9月24日 21:38:08 修改一些描述,不影响步骤。
  • 2017年9月14日 11:20:52 教程基本完工。更新了github上的代码。更新参考资料链接。
  • 2017年9月13日 22:57:09 expect脚本基本完工,继续更新本文。将更新github上本项目代码。明天再写。
    2017年9月13日 22:01:56 实现各种常见下载时问题的报错红字提示。
    2017年9月12日 20:24:06 实现下载过程error、warn、Succeed三种声音提示。优化代码快完成。
    2017年9月11日 实现一键run/debug!,美滋滋。。run配置完成。速度 rebuild 2s~3s,少量改动再build则 5s,expect脚本约2s 496ms,总5s左右。
  • 2017年9月10日 11:29:27更正文章部分描述错误。继续写CLion调试最后的步骤
  • 2017年9月9日 22:27:23 写到三种调试方式,优化在CLion中的OpenOCD和WriteFlash配置。
  • 2017年9月9日 11:25:05 实现在CLion中调用OpenOCD和ActiveTcl 的expect进行下载调试,速度20s左右比较慢(【更正】:其实烧录下载不慢,是timeout设置过长拖了10s才打开gdb)。继续写本教程。
  • 2017年9月8日 19:53:22 教程写到创建eclipse项目。。
  • 2017年9月1日 成功在CLion中编译STM32项目,未实现在CLion中直接调用OpenOCD进行调试。

环境

截止至2017年9月7日 22:21:01,本文所使用的软硬件工具如下,都是尽量最新版:

  • 电脑系统:win7x64
  • IDE:
    • CLion 2017.xxx
    • Eclipse Oxygen
  • toolchain / 编译器:GNU GCC for ARM (gcc-arm-none-eabi)
  • Debugger:OpenOCD-20170821、Ozone
  • MinGW:装在C:\MinGW
    ——————
  • MCU:STM32F103RBT6,Alientek Mini v2.0开发板
  • 仿真器:J-Link-ARM v8

基本环境搭建和配置

安装CLion

下载地址https://www.jetbrains.com/clion/。最新版本CLion 2017.2.2 但该版本有一bug:打字时不能显示中文输入法的输入界面(当然打五笔的盲输不慌,哈),只装了QQ五笔其他中文输入法没试过应该也显示不了。不习惯的可以用1.2.x版本就能打中文,但新版新功能多一些。
推荐的基本配置方法见我之前文章: JetBrains公司系列IDE开发工具通用初始配置推荐(Android Studio、InterlliJ、PHPStorm、CLion、Webstorm等)

安装 MinGW:

官网是http://www.mingw.org/下载链接
装在C:\MinGW,典型安装就行。后面编译时少了mingw中的啥工具就再补。

安装 GNU Tools for ARM Embedded Processors

这是由 ARM 维护的一套工具,包括 GCC 编译器,GDB 调试工具等,在此下载和安装GNU Tools for ARM Embedded Processors
我是下载了最新的gcc-arm-none-eabi-5_4-2016q3-20160926-win32.exe,我电脑64位的,可以用。

安装 Eclipse 及插件CDT GNU Cross Development Tools

安装 Eclipse OXYGEN
安装插件:下载插件离线包 https://sourceforge.net/projects/gnuarmeclipse/files/Current%20Releases/3.x/ ,最新为 ilg.gnuarmeclipse.repository-3.4.1-201704251808.zip
然后单击菜单栏中的 Help -> Install New Software… ,单击Add...,Archive...,添加刚下载的zip文件,确定后在列表中勾选插件名,从中安装 CDT GNU Cross Development Tools.
//TODO

后面的调试部分有两个方案,各需下载不同工具,下载地址和配置见后面各方案。

创建项目

注:看了CLion for embedded development里用到STM32CubeMx(使用详解)STM32CubeMx是STM32系列单片机初始化代码项目生成工具,支持的IDE有如下图


其中SW4STM32是基于Eclipse的IDE,不建议用它,而用Eclipse OXYGEN原版+GNU 插件,因为相对于专版的SW4STM32,Eclipse里可以写更多东西,加其他通用插件。
扯回来,说到STM32CubeMx,试了下在CLion里编译老报各种错,所以先不用它了,它的好处是设计独立单板PCB时对引脚等繁琐配置的简化操作,修改更新配置也方便,但对于用开发板的就先用不着它,因为开发板一般都有配套相关模块配置代码库。等到设计单板时再用它生成配置代码(选用上图的Other Toolchains,代码最简,应该可配合在同一CLion项目目录下使用)。

1.创建Eclipse项目

Step1 File->new C Project,如下红框,强烈建议路径命名C:\Code\Embedded\STM32\pro,原因参见我的文章代码的文件和路径命名规范和目录结构规划


因为在Eclipse建立一个基于Stm32f10x的project以后的项目都可以直接复制这个项目文件夹修改成新名称的项目,所以我直接建个pro_tmpl_stm32f10x项目当模板,如下图名称和路径。

pro_tmpl_stm32f10x

Step2 接下来如图,我开发板芯片是stm32f103rbt,对应Chip family是Medium Density。Content选择Empty吧,blink a led示例代码没啥好参考。对于Trace output应该是初始给你生成trace_puts、trace_printf函数之类代码的c文件,好在Debug时电脑调试框能返回类似printf的打印信息。其他默认,我不认识。

Paste_Image.png

如果不知道Chip family选哪个可以打开STM32CubeMx,新建项目,找到自己芯片后看其英文介绍第一句就提示了其封装密度类型:

查找密度类型

Step3 一路Next,到这选如图配置,点Finish

step3

2.转为CLion项目,在CLion中编译

CLion中打开文件夹C:\Code\Embedded\STM32\pro_tmpl\pro_tmpl_stm32f10x

根目录下新建CMakeLists.txt,代码内容如下(自行修正路径)

project(pro_tmpl_stm32f10x)
cmake_minimum_required(VERSION 3.3)
enable_language(ASM)

set(BUILD_MODE DEBUG)
#在Clion中Settings>Build>Cmake的Cmake options加入下面一行(自行修正路径),不包含#符号。
#-DTOOLCHAIN_DIR="C:\Program Files (x86)\GNU Tools ARM Embedded\6 2017-q2-update" -DCMAKE_TOOLCHAIN_FILE="toolchain-arm-eabi-gcc.cmake"

set(USER_C_FLAGS "-mcpu=cortex-m3 -mthumb -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall")
set(USER_CXX_FLAGS "-std=c++11 -fabi-version=0 -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-threadsafe-statics")

#下面可选 -DSTM32F10X_MD -DSTM32F10X_HD等。一般选高点的_HD也不影响运行。
set(USER_C_DEFINES
        "-DARM_MATH_CM3 \
    -DUSE_FULL_ASSERT \
    -DTRACE \
    -DOS_USE_TRACE_SEMIHOSTING_STDOUT \
    -DSTM32F10X_MD \
    -DUSE_STDPERIPH_DRIVER \
    -DHSE_VALUE=8000000"
        )

if (BUILD_MODE STREQUAL "DEBUG")
    set(USER_C_FLAGS "${USER_C_FLAGS} -Og -g3")
    set(USER_C_DEFINES "${USER_C_DEFINES} -DDEBUG")
else ()
    set(USER_C_FLAGS "${USER_C_FLAGS} -O3")
endif ()

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${USER_C_FLAGS} ${USER_C_DEFINES}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USER_C_FLAGS} ${USER_CXX_FLAGS} ${USER_C_DEFINES}")


include_directories(
        ${TOOLCHAIN_DIR}/arm-none-eabi/include
        ${TOOLCHAIN_DIR}/arm-none-eabi/include/c++/6.3.1
        ${TOOLCHAIN_DIR}/arm-none-eabi/include/c++/6.3.1/arm-none-eabi/thumb/v7-m
        ${TOOLCHAIN_DIR}/arm-none-eabi/include/c++/6.3.1/backward
        ${TOOLCHAIN_DIR}/lib/gcc/arm-none-eabi/6.3.1/include
        ${TOOLCHAIN_DIR}/lib/gcc/arm-none-eabi/6.3.1/include-fixed
)
include_directories(
        #STM32固件库
        system/include
        system/include/cmsis
        system/include/stm32f1-stdperiph
        #ALIENTEK开发板的代码库
        board/ALIENTEK/System/delay
        board/ALIENTEK/System/sys
        board/ALIENTEK/System/usart
        #ALIENTEK开发板的模块代码库
        board/ALIENTEK/MiniSTM32Board/LED
        board/ALIENTEK/MiniSTM32Board/KEY
        #自己的include
        user/include
)

file(GLOB_RECURSE C_FILES
        #stm32固件库
        system/*.c system/*.cpp
        #ALIENTEK开发板的代码库
        board/*.c board/*.cpp
        #自己的
        user/*.c user/*.cpp
)

set(SOURCE_FILES ${C_FILES})

set(CMAKE_EXE_LINKER_FLAGS
        "${CMAKE_EXE_LINKER_FLAGS} -L\"${PROJECT_SOURCE_DIR}/ldscripts\" -T libs.ld -T mem.ld -T sections.ld -fmessage-length=0 -fsigned-char -ffreestanding -nostartfiles -specs=nano.specs -Xlinker --gc-sections -Wl,-Map=${PROJECT_NAME}.map")

add_executable(${PROJECT_NAME}.elf ${SOURCE_FILES})
set(HEX_FILE ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bin)
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
        COMMAND ${CMAKE_OBJCOPY} -Oihex $<TARGET_FILE:${PROJECT_NAME}.elf> ${HEX_FILE}
        COMMAND ${CMAKE_OBJCOPY} -Obinary $<TARGET_FILE:${PROJECT_NAME}.elf> ${BIN_FILE}
        )

项目根目录下新建toolchain-arm-eabi-gcc.cmake,代码内容如下

include(CMakeForceCompiler)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m3)

find_program(ARM_CC arm-none-eabi-gcc ${TOOLCHAIN_DIR}/bin)
find_program(ARM_CXX arm-none-eabi-g++ ${TOOLCHAIN_DIR}/bin)
find_program(ARM_OBJCOPY arm-none-eabi-objcopy ${TOOLCHAIN_DIR}/bin)
find_program(ARM_SIZE_TOOL arm-none-eabi-size ${TOOLCHAIN_DIR}/bin)
# specify the cross compiler指定交叉编译器
CMAKE_FORCE_C_COMPILER(${ARM_CC} GNU)
CMAKE_FORCE_CXX_COMPILER(${ARM_CXX} GNU)

set(CMAKE_ARM_FLAGS
        "-mcpu=cortex-m3 -mthumb -fno-common -ffunction-sections -fdata-sections"
        )

if (CMAKE_SYSTEM_PROCESSOR STREQUAL "cortex-m3")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_ARM_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_ARM_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_ARM_FLAGS}")
else ()
    message(WARNING
            "Processor not recognised in toolchain file, "
            "compiler flags not configured."
            )
endif ()

# fix long strings (CMake appends semicolons)
string(REGEX REPLACE ";" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "")

set(BUILD_SHARED_LIBS OFF)

在Clion菜单中File>Settings>Build....>Cmake的Cmake options加入下面一行(自行修正路径)

-DTOOLCHAIN_DIR="C:\Program Files (x86)\GNU Tools ARM Embedded\6 2017-q2-update" -DCMAKE_TOOLCHAIN_FILE="toolchain-arm-eabi-gcc.cmake"

点击Tools>CMake>ResetCache and Reload Project,当你每次编译报错时,可以第一步用这命令重清下缓存再试。
打开View>ToolWindows>Cmake窗口,点击Tools>CMake>Reload CMake Project,一般会报一些warning,最后会提示

-- Configuring done
-- Generating done
CMake Warning:
Manually-specified variables were not used by the project:
CMAKE_TOOLCHAIN_FILE
-- Build files have been written to: C:/Code/Embedded/STM32/pro_tmpl/pro_tmpl_stm32f10x/cmake-build-debug

再Run>Build,第一次编译整个目录会比较久,后面编译就自动只编译修改过的文件。buil成功最后会提示:

[100%] Built target pro_tmpl_stm32f10x.elf

然后查看cmake-build-debug下有.bin和.hex生成,就算build成功了。

我把根目录的include和src放新建的user文件夹了,以和system、board对立区分。放张目录图:

项目目录

根目录中board放的是开发板的库,如上图有ALIENTEK的Mini开发板提供的常用库System和模块或驱动:KEY、LED。至于user、board标蓝色,system标黄色,用右键就可以标。
注:不建议把Stm32核心库system文件夹和开发板模块文件等放在项目外部链接的方式。原因是1.我试过多种方法编译stm32库为静态库然后在项目里链接编译老是报各种错。2.system里放的是.c源代码不是预编译好的.s汇编文件或.a静态库文件,比较利于学习底层代码,代码调试时也容易定位深层代码查找bug原因。3.代码项目总量就10几20MB,不算大。

烧写和调试

有两种方案,
方案一是Eclipse或Segger Ozone(旧名J-LinkDebugger)中调试,
方案二是CLion中调用OpenOCD和Tcl expect脚本直接烧写后启动内部gdb窗口进行调试,
但由于CLion IDE尚不完善,gdb不是IDE原生支持的,所以在调试过程中有些不便之处,比如调试不能查看内存、寄存器,不能查看汇编,但一般不需要用到。//TODO不知道新版本CLion有改进没,我再看看。
由于两种方案在win下所用的J-Link仿真器驱动不同,所以
方案一和二同时只能二选一!!。(//TODO我再找找有没其他兼容的方法。)

方案一中Eclipse调试方法:

单击菜单栏中的 Run -> Debug Configurations… 在左侧的 GDB SEGGER J-Link Debugging下的xxxDebug右键Duplicate复制一个新的配置,修改如图红框:


然后Debug既可。

方案一中Segger Ozone调试方法:

Ozone>File -> New -> New Project Wizard…


新建Ozone项目后File -> Save Project As…, 选择CLion项目根目录,保存名为 run.jdebug 。
在CLion>File>Settings>Tools>External Tools,右侧新建Tool,配置如图


以后在CLion中可以Tools>External Tools>Ozone Debugger,就能打开Ozone手动调试。

方案二:CLion中直接烧写调试

安装Jlink仿真器驱动程序

上面提到,这个驱动比较特别,与默认自动装的jlink驱动似乎有些不同,只能搭配OpenOCD使用,对Eclipse Ozone 会报错。
登陆网站http://zadig.akeo.ie/下载zadig软件,下载速度太慢,可另点通用USB驱动Zadig 2.3.701 绿色版

引用openOCD的使用1: 应用openOCD和Jlink仿真器连接Freescale K60系列 MCU文章所说:

说的直白一些openOCD只把Jlink仿真器当作普通的USB设备来使用,不使用Jlink自带的仿真器驱动程序,如果已安装了Jlink仿真器驱动程序,这个过程就是把原先的驱动程序换掉。

直接引用文章步骤:

  • 插入Jlink仿真器(如果是第一次插入Jlink仿真器,系统会要求安装驱动程序,我们可以点击取消,不必理会),运行zadig软件。
  • 选择Options-List All Devices。
  • 在其下拉列表中选择J-Link。
  • 选择WinUSB驱动程序,点击Reinstall Driver按钮或Replace Driver按钮,这样便完成了驱动程序的替换。

如下图:


Zadig
安装配置OpenOCD

打开http://gnutoolchains.com/arm-eabi/openocd/下载安装。这里用最新版本OpenOCD 0.10.0 [2017-08-21][OpenOCD-20170821.7z]。安装路径推荐放C:\Code\Embedded\env\OpenOCD\
在CLion>File>Settings>Tools>External Tools,右侧新建Tool,配置如图


其中-f interface/jlink.cfg -f target/stm32f1x.cfg,jlink.cfg表示使用jlink仿真器,stm32f1x.cfg表示下载到的系列MCU。在OpenOCD\OpenOCD-20170821\share\openocd\scripts目录可以找到各类cfg配置文件。
注:如果使用LCD等模块其引脚与jtag接口冲突,则需要在C:\Code\Common\Environment\OpenOCD\OpenOCD-20170821\share\openocd\scripts\interface\jlink.cfg脚本中的interface jlink这行下方加上一行:

#transport select jtag
transport select swd 

就把jlink的默认jtag模式改为swd模式,只占用2个IO引脚。

运行Run>Extral tools>OpenOCD,提示如下则成功连接了仿真器:

........
Info : No device selected, using first device.
Info : J-Link ARM V8 compiled Nov 28 2014 13:44:46
Info : Hardware version: 8.00
Info : VTarget = 3.319 V
Info : clock speed 1000 kHz
Info : JTAG tap: stm32f1x.cpu tap/device found: 0x3ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x3)
Info : JTAG tap: stm32f1x.bs tap/device found: 0x16410041 (mfg: 0x020 (STMicroelectronics), part: 0x6410, ver: 0x1)
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

如提示如下连接失败,则关闭gdb等正在运行的项,再次运行OpenOcd。或Ctrl+Shift+Esc,关闭openocd进程。再不行,则win任务栏右下角那点弹出J-link, 重插USB线再试:

Open On-Chip Debugger 0.10.0 (2017-08-21) [https://github.com/sysprogs/openocd]
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
jtag_ntrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Warn : Failed to open device: LIBUSB_ERROR_ACCESS.
Info : No device selected, using first device.
Error: No J-Link device found.
安装ActiveTcl的expect并配置成CLion外部工具

Mac系统自带expect命令,但在Windows系统要装ActiveState提供的Expect for Windows(不推荐在此下载)
参考关于expect在Windows上的安装和使用,可直接下载expect压缩包(推荐),解压到C:\Code\Embedded\env\Expect-5.21r1b1\
再在C:\Code\Embedded\env目录下建立一个公共的配置文件writeflash.expect.sh,这是供expect命令使用的,功能是telnet连上OpenOCD,交互式输入烧写、halt等命令。内容如下,比较长。(可在CLion中安装sh插件以高亮sh代码)
更新-2017年9月13日 从十几行改到150行,最好在github我的库里拷贝会比较新:)

#!/usr/bin/expect
#author: @fightfa
#2017年9月13日 22:01:56 实现各种常见下载时问题的报错红字提示。
#2017年9月12日 20:24:06 声音提示实现。优化代码快完成。
#2017年9月11日 实现一键run/debug,run配置完成。速度 rebuild 2s~3s,少量改动build则 5s,本脚本2s 496ms,总5s左右
#必看说明:!!!
#教程详见:http://www.jianshu.com/p/f8a939ad7efd
#CLion中Run/Debug的romote debug和romote run互相切换时不能先自动关闭gbd窗口中原已运行的Tab,要手动关闭!# TODO 解决左边说的。
#Run/debug Configuraton 中remote debug中必须手动指定Symbol file即elf文件路径,emote run中则Symbol file置空。
#窗口输出文字都用了英文写。
set now [clock clicks]
set timeout 8
#TODO * 传入elf路径参数给CLion中的gdb remote 再使CLion开gdb窗口。(难实现。)
#TODO 优化参数传入格式
#TODO  处理可能的因地址含有空格而须带双引号的参数处理的问题
#注:将传入的地址中所有的左斜/替换为右斜\,由于在win中地址是\\左斜,linux mac中expect shell等只支持/右斜的地址,expectForWin也是
set num [regsub -all "\\\\" [lindex $argv 0] "\/" projectFileDir]
set num [regsub -all "\\\\" [lindex $argv 1] "\/" bin_path]
#action:执行动作:-Run或 -Debug
set action [string tolower [lindex $argv 2]]
set bin_path $projectFileDir/$bin_path

#必须是严格格式《空格{》《}空格》。。
# 保持 } elseif 才能不报错。。
if {$action=="-debug"} {
    send_user ">>Action is Debug\n"
} elseif {$action=="-run"} {
    send_user ">>Action is Run\n"
} else {
    set action "-debug"
    send_user "\n>>warn: Unkown Action of \'$action\', now is default to Debug\nYou can set argv\[1\]  \'-run\' or \'-debug\' \n"
}

global MessageBeep
set MessageBeep $projectFileDir/MessageBeep.exe
proc beepError {} {
    global MessageBeep
    exec $MessageBeep 16
}
proc beepSucceed {} {
    global MessageBeep
    exec $MessageBeep 64
}
#proc beepOk{} {
#    global MessageBeep
##    0 这声音其实更常用在warn上。。
#    exec $MessageBeep 0
#}

#发出铛一声,提示build完成
#beepOk

#结束与openocd会话的函数:

proc exitSession {code} {
    #结束会话,不结束会话可能会导致下次烧录时send出错吧#TODO 验证左边。
    send "exit\n"
    expect "Connection closed by foreign host"
    set timeout 0
    exit $code
}
#TODO 不能是send_error $expect(0,string) 原因未知
#TODO send_error 偶尔显示不出来就exit了 原因未知
proc abort {str} {
    beepError
    send_error \n$str
    global timeout
    exitSession 1
}
proc abortTimeout {} {
    global timeout
    beepError
    puts "\n"
    send_error "\n>>error:timeout ${timeout}s! please check the above, or:\nyou may try extend the timeout value (or set -1 to be not timeout) and run again."
    exitSession 2
}
proc abortCatch {str} {
    beepError
    send_error "\n>>error:process is interrupted because catch the follow from above :"
    send_user \n$str
    global timeout
    exitSession 1
}
proc abortWithTips {} {
        abort "failed! please check the above, or:\n1.Power on your board;\n2.Rerun the tool OpenOCD\n3.Modify or comment this line in expect.writeflash.sh and rerun"
}
#步骤1:登录建立与会话
spawn telnet localhost 4444
#不能如下行这样写,会卡上3、4秒。。。
#expect { ">" {}
expect {
    -re "\ntelnet: Unable to connect to remote host.*\n>" {
        abortWithTips
    }
    ">" {}
    timeout {abortTimeout}
}
#步骤2:复位板子
send "reset init\n"
#send "reset\n"
#send "init\n"
#send "halt\n"
#timeout {abortTimeout}  这句放前面会慢4s。。。
#不能用^或\A匹配开头。。
expect {
   -nocase -re "\n(error|failed).*\n>" {
        abortWithTips
    }
    ">" {}
     timeout {abortTimeout}
}
#步骤3:烧录bin文件到板子
send "flash write_image erase $bin_path 0x8000000\n"
expect {
        -re "\nwrote(.*)>" {
            #发出铛一声,提示烧写完成
            beepSucceed
        }
        -re "\n(error|failed|couldn't open.*)>" {
            abortCatch $expect_out(1,string)
        }
        timeout {abortTimeout}
}
#TODO 上一指令和下面几个指令偶尔运行会出现多一个 >字符。导致显示信息有时重复和乱了行顺序。。影响不大。
#步骤4:烧录完发下面指令给板子
if {$action=="-debug"} {
#下载完断点调试:
    send "halt\n"
    expect ">"
    #打开调试通信模式,可以CLion下方的OpenOCD窗口中看到接收trace_puts等的返回信息,这里必须加。
    send "arm semihosting enable\n"
    expect ">"
} elseif {$action=="-run"} {
#下载完直接运行,其中edit configurations->remote run 中 Symbol file必须保持为空
#这两行可不加:
#    send "reset\n"
#    expect ">"
#打开调试通信模式,可以在CLion下方的OpenOCD窗口中看到接收trace_puts等的返回信息,若不用到则可关。
    send "arm semihosting enable\n"
#    send "arm semihosting disable\n"
    expect ">"
}
#步骤5:计算本脚本运行时间,不包括Build时间 #TODO * 计入Build时间的方法
set time [expr [clock clicks]-$now]
set seconds [expr $time/1000]
set millis [expr $time%1000]
set now [clock format [clock seconds] -format {%H:%M:%S}]
#send_user "\n>>w 烧写成功!耗时 ${seconds}s ${millis}ms\n"
send_user "\n>>$now Write Flash finished in ${seconds}s ${millis}ms\n"

#expect off
exitSession 0

其中发出声音提示是调用了Windows的API MessageBeep,由于MessageBeep函数参数不带句柄回调,所以直接在expect中写
exec "C:/windows/system32/cmd.exe" "/c rundll32 user32 MessageBeep "(最后有个空格)或在cmd中打rundll32 user32 MessageBeep
是可以直接发声,但发声随机,不能指定。所以我开CLion写了个简单的MessageBeep.exe程序,如下

#include <windows.h>

/*expect脚本中使用方法示例:
 exec "MessageBeep.exe"  48
 常见声音提示:
   #define MB_OK                            0
   #define MB_ICONWARNING                    48
   //   ASTERISK :星号
   #define MB_ICONASTERISK                 64
   #define MB_ICONERROR                    16
    */
int main(int argc, char *argv[]) {
    UINT beep = MB_ICONERROR;
    if (argc > 1) beep = (UINT) atoi(argv[1]);
    MessageBeep(beep);
    return 0;
}
//expect脚本中直接运行:(只能发一种不确定声音)
// exec "C:/windows/system32/cmd.exe" "/c rundll32 user32.dll MessageBeep "
// 只能发一种不确定声音:
//    system("rundll32 user32.dll,MessageBeep ");

编译成MessageBeep.exe后复制到stm32项目根目录下即可供expect脚本调用。也可直接在我github项目上复制。

个人啰嗦PS:
1.为了写set num [regsub -all "\\\\" $bin_path0 "\/" bin_path]这一行实现替换字符串效果,我可瞎找了一个晚上试了近一百行代码啊。。最终在这找到解决思路:Tcl 编程简介。ActiveTcl expect和其他系统的expect在语法上还是有不少不同的。。。。
2.为实现expect中发出声音提示,尝试了不少思路,如运行bat、vbs、脚本等。

然后在CLion中File>Settings>Tools>External Tools,右侧新建Tool,命名为Write Flash & Run,配置如图:

writeflash tool

Parameters为$ProjectFileDir$\expect.writeflash.sh $ProjectFileDir$ cmake-build-debug\$ProjectName$.bin -run
路径使用了CLion的$xx$参数(注意$xx$参数只有在Build运行后才能使用),比较通用,避免建了新项目又得改这里。
路径中尽量用右斜,windows里的路径\左斜默认表示法真是坑人啊。
再选中Write Flash & Run,点击右上方复制图标,生成新tool,命名为Write Flash & Debug,配置同上,但Parameters为$ProjectFileDir$\expect.writeflash.sh $ProjectFileDir$ cmake-build-debug\$ProjectName$.bin -debug

接下来是Run>Edit Configurations...中,添加gdb remote配置:


其中Symbol file必须置空,这样执行Run可烧录完直接让板子运行而忽略所有代码断点。记得勾选single instance only。下方添加的工具是点击绿色+号出现的前两项。
再复制remote run为remote debug,修改如下部分:


其中Symbol file是你生成的elf文件路径:C:/Code/Embedded/STM32/pro_tmpl/pro_tmpl_stm32f10x/cmake-build-debug/pro_tmpl_stm32f10x.elf

好了,现在你打开OpenOCD tool,(打开一次且链接上仿真器了就行)
再运行 romote run:



如下,编译烧录成功,板子运行了!



OpenOCD显示了板子传回的“hello stm”的信息:

我在main.c中写的demo是:
int
main(int argc, char *argv[]) {
    int i = 0;
    delay_init();             //延时函数初始化
    LED_Init();            //初始化与LED连接的硬件接口

    //CLion调试时在OpenOCD窗口console中有返回信息:
    trace_puts("hello stm32");
    for (int i = 0; i < 5; i++) {
        LED0 = 0;
        LED1 = 0;
        delay_ms(300);     //延时300ms
        LED0 = 1;
        LED1 = 1;
        delay_ms(300);    //延时300ms
    }
    while (1) {
        i = ++i % 6;
        LED0 = 1;
        LED1 = 1;
        delay_ms(300);     //延时300ms
        LED0 = 1;
        LED1 = 0;
        delay_ms(300);    //延时300ms
        if (i == 3) {
            LED0 = 0;
            delay_ms(300);    //延时300ms
        }
    }
}

切换到romote debug前,先将gdb的romote run进程结束掉,再选择remote debug,运行:



可以进行调试了,断点、跟踪变化等。但试了中途改变量值继续运行会没变化,可能clion gdb工具里还没实现直接修改寄存器的功能,也未有查看变量地址等功能。应该CLion以后版本会支持吧。

OpenOCD,remote run/debug等都可设置快捷键。推荐Shift+Alt+D打开openocd,Shift+Alt+X编译烧录,Alt+2为Cmake窗,Alt+3为Build窗,等等。

最后一步是把项目pro_tmpl_stm32f10x上传到github仓库了,点这里

好了,现在终于可以愉快的敲C代码了!

相关资料

//TODO

参考文章

使用CLion做嵌入式开发
CLion for embedded development
使用 Eclipse 和 ARM GCC 搭建 STM32 开发环境
OpenOCD User’s Guide
gdb + openocd 调试嵌入式软件
expect脚本相关:
Tcl 编程简介
expect学习笔记及实例详解
//TODO

推荐阅读更多精彩内容