1.IDE背后的命令
1.1 IDE是什么
IDE指集成开发环境(Integrated Development Environment)。
我们开发STM32F103等单片机程序时使用是keil就是一种IDE。
使用IDE,很容易操作,点点鼠标就可完成:
添加文件
指定文件路径(头文件路径、库文件路径)
指定链接库
编译、链接
下载、调试
1.2 IDE的背后是命令
现场使用keil来演示命令的操作。
注意
使用GitBash执行命令的话
由于GitBash采用类似Linux的文件路径表示方法(比如 /d/abc,而非 d:\abc),命令行中windows格式的路径名要加上双引号,比如".\objects\main.o"
使用dos命令行执行命令的话
不需要加双引号
在某个Keil工程所在目录下,打开Git Bash:
doc_and_source_for_mcu_mpu\STM32MF103\source\02_录制视频时现场编写的源码\01_led_c
编译main.c
执行命令:
"C:\Keil_v5\ARM\ARMCC\Bin\ArmCC" --c99 --gnu -c --cpu Cortex-M3 -D__EVAL -g -O0 --apcs=interwork --split_sections -I.\RTE\_led_c -I"C:\Users\thisway_diy\AppData\Local\Arm\Packs\Keil\STM32F1xx_DFP\2.3.0\Device\Include" -I"C:\Keil_v5\ARM\CMSIS\Include" -D__UVISION_VERSION="527" -DSTM32F10X_HD -o ".\objects\main.o" --omf_browse ".\objects\main.crf" --depend ".\objects\main.d" "main.c"
编译start.S
执行命令:
"C:\Keil_v5\ARM\ARMCC\Bin\ArmAsm" --cpu Cortex-M3 --pd "__EVAL SETA 1" -g --apcs=interwork -I.\RTE\_led_c -I"C:\Users\thisway_diy\AppData\Local\Arm\Packs\Keil\STM32F1xx_DFP\2.3.0\Device\Include" -I"C:\Keil_v5\ARM\CMSIS\Include" --pd "__UVISION_VERSION SETA 527" --pd "STM32F10X_HD SETA 1" --list ".\listings\start.lst" --xref -o ".\objects\start.o" --depend ".\objects\start.d" "start.s"
链接
执行命令:
"C:\Keil_v5\ARM\ARMCC\Bin\ArmLink" --cpu Cortex-M3 ".\objects\main.o" ".\objects\start.o" --ro-base 0x08000000 --entry 0x08000000 --rw-base 0x20000000 --entry Reset_Handler --first __Vectors --strict --summary_stderr --info summarysizes --map --load_addr_map_info --xref --callgraph --symbols --info sizes --info totals --info unused --info veneers --list ".\Listings\led_c.map" -o ".\Objects\led_c.axf"
1.3. 提出几个问题
头文件在哪?
库文件在哪?库文件是哪个?
源文件有哪些?
源文件怎么编译?可以指定编译参数吗?
多个源文件怎么链接成一个可执行程序?
有a.c, b.c, c.c,我只修改了a.c,就只需要编译a.c,然后在链接:怎么做到的?
1.4. 要解决这些疑问,需要了解命令行
如果你只学习单片机,只想使用keil,当然可以不学习命令行。
但是如果想升级到Linux、各类RTOS,需要掌握命令行。
1.5. 有两套主要的编译器
armcc
ARM公司的编译器
keil使用的就是armcc
GNU工具链
Linux等开源软件经常使用gcc
后面以GNU工具链为例讲解,所涉及的知识可以平移到armcc上。
2.准备工作
2.1. arm-linux-gcc和gcc是类似的
arm-linux-gcc
给ARM芯片编译程序
gcc
在x86编译程序
用法基本一样
为方便演示,我们使用gcc
为了方便在windows下演示,我们使用Code::Blocks
它的安装程序自带gcc
2.2. Code::Blocks
它是一款基于GCC的windows IDE,可以用来开发C/C++/Fortran。
官网地址:http://www.codeblocks.org/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYAE27YT-1649769892187)(lesson\gcc\003_download_codeblocks.png)]
在我们提供的GIT仓库里也有:git clone https://e.coding.net/weidongshan/noos/cortexA7_windows_tools.git
下载GIT后,在apps目录下。
2.2.1 安装
双击安装。
2.2.2 设置windows环境变量
在Path环境变量中添加:C:\Program Files\CodeBlocks\MinGW\bin
2.2.3 命令行示例
启动Git Bash,编译程序hello.c:
#include <stdio.h> int main(void) { printf("hello, world!\n"); return 0; }
编译、运行命令如下:
gcc -o hello hello.c ./hello.exe
3. gcc编译过程详解
3.1. 程序编译4步骤
我们经常使用“编译”泛指上面的4个步骤之一,甚至有时候会囊括这四个步骤。
3.2. gcc的使用方法
gcc [选项] 文件名
3.2.1 gcc使用示例
gcc hello.c // 输出一个名为a.out的可执行程序,然后可以执行./a.out gcc -o hello hello.c // 输出名为hello的可执行程序,然后可以执行./hello gcc -o hello hello.c -static // 静态链接 gcc -c -o hello.o hello.c // 先编译(不链接) gcc -o hello hello.o // 再链接
3.2.2 gcc常用选项
3.2.2.1 手工控制编译过程
选项 | 功能 |
-v | 查看gcc编译器的版本,显示gcc执行时的详细过程 |
-o | 指定输出文件名为file,这个名称不能跟源文件名同名 |
-E | 只预处理,不会编译、汇编、链接t |
-S | 只编译,不会汇编、链接 |
-c | 编译和汇编,不会链接 |
一个c/c++文件要经过预处理、编译、汇编和链接才能变成可执行文件。
(1)预处理
C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。
(2)编译
编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码。
(3)汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。“反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。
(4)链接
链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件。
hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello
详细的每一步命令如下:
gcc -E -o hello.i hello.c gcc -S -o hello.s hello.i gcc -c -o hello.o hello.s gcc -o hello hello.o
上面一连串命令比较麻烦,gcc会对.c文件默认进行预处理操作,使用-c再来指明了编译、汇编,从而得到.o文件,
再将.o文件进行链接,得到可执行应用程序。简化如下:
gcc -c -o hello.o hello.c gcc -o hello hello.o
3.2.2.2 使用后缀名决定编译过程
参考《嵌入式Linux应用开发完全手册》:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C6TVJjQq-1649770002021)(lesson\gcc\002_gcc_default_operation.png)]
总结
输入文件的后缀名和选项共同决定gcc到底执行那些操作
在编译过程中,最后的步骤都是链接
除非使用了-E、-S、-c选项
或者编译出错阻止了完整的编译过程
3.2.2.3 指定头文件目录
头文件在哪里?
系统目录
系统目录在哪?工具链里的某个include目录
怎么确定?
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)
可以不使用系统include目录吗?可以,编译时指定参数-nostdinc
可以自己指定头文件目录
-I <头文件目录>
3.2.2.4 指定库文件
库文件在哪里?
系统目录
系统目录在哪?工具链里的某个lib目录
怎么确定?
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)
可以不使用系统lib目录吗?可以,编译时指定参数-nostdlib
可以自己指定库文件目录
-L <库文件目录>
指定库文件
-l <abc> // 链接 libabc.so 或 lib.a
3.3. 开发板程序编译示例
最后链接时,使用arm-linux-ld
而不是使用arm-linux-gcc
前者可以完全自己指定所连接的文件
后者会链接一些默认的启动文件
3.4. 参考书籍
《嵌入式Linux应用开发完全手册》中的《3.1 交叉编译工具选项说明》
4.makefile
4.1 Makefile的引入及规则
使用keil, mdk,avr等工具开发程序时点击鼠标就可以编译了,它的内部机制是什么?它怎么组织管理程序?怎么决定编译哪一个文件?
答:实际上windows工具管理程序的内部机制,也是Makefile,我们在linux下来开发裸板程序的时候,使用Makefile组织管理这些程序,本节我们来讲解Makefile最基本的规则。Makefile要做什么事情呢?
组织管理程序,组织管理文件,我们写一个程序来实验一下:
文件a.c
02 #include <stdio.h> 03 04 int main() 05 { 06 func_b(); 07 return 0; 08}
文件b.c
2 #include <stdio.h> 3 4 void func_b() 5 { 6 printf("This is B\n"); 7 }
编译:
gcc -o test a.c b.c
运行:
./test
结果:
This is B
gcc -o test a.c b.c
这条命令虽然简单,但是它完成的功能不简单。
我们来看看它做了哪些事情,
我们知道.c程序 ==》 得到可执行程序它们之间要经过四个步骤:
1.预处理
2.编译
3.汇编
4.链接