这篇文章为本人结合网上教程,自行探索,如有错误敬请谅解

出于不想浑浑噩噩度过大学生活的缘由,我加入了学院的实验室。初学的是嵌入式,也是从51、STM32开始入门。
学习嵌入式,不可避免的便是开发环境的搭建。而无论是网上查阅,还是学长推荐,绕不过去的总是 KEIL。不能否认,KEIL MDK集代码编辑、编译、下载、调试为一体,为初学者提供了非常友好的图形操作界面。

MDK (Microcontroller Development Kit) 是KEIL公司的主打产品,是RealView MDK的简称。Keil公司由两家私人公司联合运营,分别是德国慕尼黑的Keil Elektronik GmbH和美国德克萨斯的Keil Software Inc,Keil公司在2005年被ARM公司收购。MDK在全球被超过10万的嵌入式工程师所使用,目前最新版本是MKD5.25,该版本使用uVision5 IDE作为集成开发环境,由于KEIL和ARM公司之间的紧密合作,使得MDK成为目前针对ARM内核的处理器,尤其是Cortex-M内核的最佳开发工具。

但是我又不得不吐槽其上一世纪的界面风格,其代码编辑、补全能力几近于无。最关键的是,它无法在GUN/LinuxMac OS上跨平台运行。 虽然我并没有Mac设备
出于以上考虑,再加上为了了解其编译过程,我便开始使用GNU/Linux下的开源工具链来搭建嵌入式开发环境。

迁移代码

嵌入式开发初学者,使用的大都是正点、原子之类的工程框架。但这些大都是面向KEIL的闭源编译器的,假如直接移植到GCC编译器上,那么看到的只能是一片红。
因此,我们首先要做的便是修改源码。

  1. 更换启动文件。
    STM32 为不同的编译环境提供了不同的启动文件。而两种不同的编译环境,自然需要不同的启动文件。

  2. 修改内核文件 core_cm3.c
    分别修改第736行于753行代码。

1
2
3
4
5
6
7
//736
- __ASM volatile ("strexb %0, %2, [%1]" : "=r" (result) : "r" (addr), "r" (value) );
+ __ASM volatile ("strexb %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );

//753
- __ASM volatile ("strexh %0, %2, [%1]" : "=r" (result) : "r" (addr), "r" (value) );
+ __ASM volatile ("strexh %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );
  1. 修改sys.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief 设置栈顶地址
* @param addr:栈顶地址
* @retval None
*/
- __asm void MSR_MSP(u32 addr)
- {
- MSR MSP, r0 // set Main Stack value
- BX r14
- }
+ void MSR_MSP(u32 addr){
+ __ASM volatile("MSR MSP, r0"); //设置主堆栈值
+ __ASM volatile("BX r14");
+ }

当然,也要修改sys.hvoid MSR_MSP(u32 addr);

  1. 修改串口打印函数
    标准库函数的默认输出设备是显示器,如果实现在串口或 LCD 输出,必须重定义标准库函数里调用的与输出。
    而为armcc编译器重定向的代码,并不适用于gcc编译器。所以需要注释掉这些代码,再引用_write函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
// #if 1
// #pragma import(__use_no_semihosting)
// //标准库需要的支持函数
// struct __FILE
// {
// int handle;
// };

// FILE __stdout;
// //定义_sys_exit()以避免使用半主机模式
// void _sys_exit(int x)
// {
// x = x;
// }
// //重定义fputc函数
// int fputc(int ch, FILE *f)
// {
// while ((USART1->SR & 0X40) == 0)
// ; //循环发送,直到发送完毕
// USART1->DR = (u8)ch;
// return ch;
// }

// #endif
int _write(int fd, char *pBuffer, int size)
{
int i;
for (i = 0; i < size; i++)
{
USART_SendData(USART1, *pBuffer++);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
;
}
return size;
}
  1. 串口缓存
    在初始化串口后,加上setvbuf(stdout, NULL, _IONBF, 0);来清楚缓存。

以上,便将工程文件修改完成了。

搭建工程环境

GNU/Make

GNU/Make是一个控制从程序的源文件中生成程序的可执行文件和其他非源文件的自动化工具,它可以通过读取包含有每个非源文件以及生成依赖规则的Makefile文件来构建程序。

我们之后需要编写Makefile文件来实现自动化构建项目。

ARM-GCC 编译链

ARM-GCC是一套交叉编译工具链家族,其命名规则统一为:arch [-vendor] [-os] [-(gnu)eabi]

  • arch
    代表芯片的体系架构,比如ARM,MIPS等。
  • vendor
    代表工具链的提供商。
  • os
    代表目标开发板所使用的操作系统。
  • eabi
    代表 Embedded Application Binary Interface,即嵌入式应用二进制接口。

ARM-GCC家族主要成员具体如下:

  • arm-none-eabi-gcc
    (ARM architecture, no vendor, not target an operating system, complies with the ARM EABI)
    主要用于编译ARM架构的裸机系统(包括ARM Linux的Boot和Kernel,不适用编译Linux应用),一般适合ARM7、Cortex-M和Cortex-R内核等芯片使用,不支持那些跟操作系统关系密切的函数。除此之外,该编译器在底层使用了newlib这个专用于嵌入式系统的C库。

  • arm-none-linux-gnueabi-gcc
    (ARM architecture, no vendor, creates binaries that run on the Linux operating system, and uses the GNU EABI)
    arm-none-linux-gnueabi-gcc是一款基于GCC,底层使用glibc库,并经过Codesourcery公司优化后所推出的编译器,其浮点运算的处理能力非常优秀,主要用于编译基于ARM架构(ARM9、ARM11以及Cortex-A9)的Linux系统(包括ARM架构的U-boot、Linux内核和Linux应用等)。

  • arm-eabi-gcc
    主要用于编译运行在Android ARM架构上的应用程序。

  • armcc
    ARM公司官方推出的编译工具,功能和arm-none-eabi-gcc类似,可以编译裸机程序(U-boot和Kernel),但是不能编译Linux应用程序。armcc一般是和ARM集成开发工具一起进行发布的,比如KEIL MDK、ADS、RVDS和DS-5中都将armcc作为自己默认的编译器,是付费软件。

我这边使用的是arm-none-eabi-gcc工具链。在Arch Linux/Manjaro 下通过 pacman -S arm-none-eabi-gcc 即可安装。

OpenOCD 调试器

OpenOCD项目最早是由Dominic Rath发起,它的目标是开发出一种能够接入市场上大多数常见MCU、MPU和FPGA等平台的通用开源片上调试器(Open On-Chip Debuger),并提供调试、系统内在线编程和边界扫描测试等功能。具体使用的时候,OpenOCD需要依靠一种叫做 调试适配器(点击这里获得OpenOCD所支持的完成设备列表)的硬件模块来帮助其在底层提供与目标板子相一致的电信号,因此只要在配置文件中对所使用的芯片和调试适配器的具体型号进行指定,OpenOCD就可以通过驱动与连接有硬件芯片的适配器进行数据通信,从而最终实现板级代码的烧写和调试。

同样的,在Arch Linux/Manjaro 下通过 pacman -S openocd 即可安装。

虽然 OpenOCD 是支持通过 St-Link 的 SWD 通信协议来烧录固件,但命令实在有些繁琐。而St-Link也有着其官方仓库
其配置过程相较于OpenOCD相对简单。

同样的,在Arch Linux/Manjaro 下通过 pacman -S stlink即可安装。接着,可以输入st-info --version来确定版本。

  • 烧录命令
    st-flash --format ihex write xxx.hex OR st-flash write xxx.bin 0x0800000
  • 擦除命令
    st-flash --reset erase

VScode 插件篇

在这个过程中,我也发现了一个实用的 VScode 插件,可以简便快捷地使用GCC开发。

VScode 是一款跨平台的开源文本编辑器。但在安装了插件后,它可以成为轻量级的IDE。

EIDE ,它既可以导入KEIL工程,作无损化迁移;又可以实现一键编译、烧录。而在搭配 cortex-debug使用后,便可以集编译、烧录、调试为一体,已经成为了一轻量级的IDE了。

总结

在最近的学习中,越发感概我国高校的STM32嵌入式教育,可能已经被KEIL等商业软件垄断。
虽然这些商业化的软件把代码编辑、编译和调试等功能集成图像化程序当中,这对于很多刚开始接触STM32开发的新手来说是非常方便的,但是其缺点也非常明显:它们阻碍了初学者对交叉编译工具链以及整个编译过程的理解。相较于软件开发,嵌入式开发更需要理解编译器的相关知识,通过编译器的命令参数来优化.bin.elf。除此之外,还应该通过配置链接脚本中TEXT段、DATA段、BSS段以及 堆、栈 的起始地址和空间容量等参数来获得定制STM32程序运行时的能力。

因此,我是建议抛弃KEIL MDK的学习模式,而转向与GNU/Linux+ARM GCC的开发环境。当然,初学只需要配置好GCC环境,仍可通过VScode插件来使用图形化界面。

接下来,我们将创建一个工程文件,并通过Makefile自动化构建。