Keil使用命令行附加预定义宏编译

1. 前言

很多时候,一份Keil工程代码可能需要满足多个不同的应用场景。可以通过逻辑判断,将多个不同的点集成在一份代码之中,但是嵌入式往往特别关注RAM空间,集成过多的逻辑判断,RAM空间可能就不够用了。针对这种情况Keil提供了配置Target,通过宏定义来分隔不同的功能,编译生成不同的bin文件。
Keil使用命令行附加预定义宏编译_第1张图片

但是有时,一个Target项目中,另外又有几个子场景,子场景也需要用预定义宏来区分开。但是Keil并没有提供这样的功能。
另外,Keil工程有非常多的Target,一次修改,必须不停选择不同Target来进行编译,这种编译方式非常不方便。

2. 方法

Keil提供了命令行,可以编译工程。另外,Keil使用的ArmCC和Armlink来编译和链接,这样的话,便可以使用makefile来编译Keil工程代码。

2.1. 命令行

2.1.1 用法

UV4 [command] [projectfile]

2.1.1.1. command

command可以是下述列表之一。如果command没有指定,则只是打开Keil工程。

  • -b, 编译指定的Keil工程默认的Target。

UV4 -b PROJECT1.uvprojx

  • -c, 清除Keil工程中所有Target的临时文件。

UV4 -c PROJECT1.uvprojx

  • -cr, 清除所有Target,然后重新编译所有Target。

UV4 -cr PROJECT1.uvprojx

  • -d, 启动Keil的调试模式。

UV4 -d PROJECT1.uvprojx

  • -f, 下载程序到flash上,下载完成之后退出。

UV4 -f PROJECT1.uvprojx -t"MCB2100 Board"

  • -r, 重新编译Keil工程的默认Target,或用-t指定Target。

UV4 -r PROJECT1.uvprojx
UV4 -r PROJECT1.uvprojx -t"Simulator"

  • -5, 转换Keil4工程为Keil5工程。

UV4 -5 myoldproject.uvproj -l log.txt

  • -et, 导出工程的Target的配置。
  • -ep, 导出工程所有Target配置。
  • -X, 生成预处理符号文件。
  • -X1, 为所有Target生成预处理符号文件。
2.1.3. Option

下面的选项是可选项。

  • -j0, 隐藏Keil的UI。
  • -i, 创建一个新的工程,或通过XML文件更新存在的工程。
  • -l logfile,保存命令生成的输出到logfile中。
  • -n device_name, 创建一个指定device_name的工程。

UV4 MyProject.uvprojx -n Device123

  • -np device_name, 如果工程不存在,则指定device_name创建工程。如果工程存在,则更新所有target的device_name。
  • -o outputfile, 指定输出的Log文件。

UV4 -r PROJECT1.uvprojx -o"output.log"

  • -q, 重新编译多工程指定的Target。
  • -t, 指定工程的target。

UV4 -r PROJECT1.uvprojx -t"MCB2100 Board"

  • -x, 配合Debug模式命令-d使用,返回完整的命令输出。
  • -y, 配合Debug模式命令-d使用,返回通用的配置。
  • -z, 重新编译工程的所有Target。

UV4 -b PROJECT1.uvproj -z

2.1.4. ERRORLEVEL

Kiel编译完成后的错误码

ERRORLEVEL 描述
0 无错误无敬告
1 仅有敬告
2 错误
3 重大错误
11 不能打开工程
12 给定的设备不存在
13 写工程文件出错
15 读XML文件出错
20 转换工程出错

2.1.5. 指定多预定义宏

为一个target指定多个预定义宏,此处使用shell脚本编写,需要git-bash或cygwin来编译。
添加一个define.h宏,Keil工程引用此宏达到控制一个Target中有不同的预编译宏。

# Obtain Keil project
prj_name=$(find *.uvproj)

# Obtain all target name of uvproj
mapfile -t target_array < <(cat "${prj_name}" | grep "TargetName" | awk -F '>' '{print $2}' | awk -F '<' '{print $1}')

# Macro
L85C_define=(__L85C1_ __L85C2__)

# Build all target
for name in "${target_array[@]}"; do
	if [[ "$name" == "L85C" ]]; then
		for define in "${L85C_define[@]}"; do
			echo "#define" $define>define.h
			# Code中include "define.h"
			"C:\Keil\UV4\UV4.exe" -r 5081Scan.uvproj -t"$name" -o "$name""$define""Build.txt" -j0
		done
	else
		"C:\Keil\UV4\UV4.exe" -r 5081Scan.uvproj -t"$name" -o "$name""Build.txt" -j0
	fi
	
	# If build error, stop build.
	if (( $? > 0 )); then
		echo Build error.
		break
	fi
done

2.2. makefile

2.2.1. Keil编译过程

Keil使用ArmCC编译源文件,使用Armlink链接目录文件生成efl文件,然后调用fromelf转换为bin文件。
那么ArmCC和Armlink如何使用呢?Keil提供一种方法来展示如何使用ArmCC和Armlink。如下图,勾选Create Batch File,会将ArmCC和Armlink编译链接的过程输出到相应的文件中。
Keil使用命令行附加预定义宏编译_第2张图片
编译当前工程指定的Test,编译连接的命令过程生成在test.bat文件中,如下图:
Keil使用命令行附加预定义宏编译_第3张图片

  • startup_mo_ia文件主要是编译汇编文件,内容为:
    Keil使用命令行附加预定义宏编译_第4张图片
    实际的调用命令为:

ArmAsm --cpu Cortex-M0 -g --apcs=interwork --pd “__MICROLIB SETA 1” -I C:\Keil\ARM\RV31\INC
-I C:\Keil\ARM\CMSIS\Include --list .\list\startup_m0.lst --xref -o .\obj\startup_m0.o --depend .\obj\startup_m0.d “startup_M0.s”

  • main._ip,这个主要是生成汇编文件,一般情况可以不使用。
  • main._i文件主要是编译.c文件,内容为:
    Keil使用命令行附加预定义宏编译_第5张图片
    实际调用的过程为:

ArmCC -c --cpu Cortex-M0 -D__MICROLIB -g -O2 -Otime --apcs=interwork -I C:\Keil\ARM\RV31\INC -I C:\Keil\ARM\CMSIS\Include -o .\obj\main.o --omf_browse .\obj\main.crf --depend .\obj\main.d “Src\main.c”

  • scan.lnp,主要是描述连接的过程。
    Keil使用命令行附加预定义宏编译_第6张图片
    实际的连接过程为:

ArmLink --cpu Cortex-M0".\obj\startup_m0.o" “.\obj\scan.o” “.\obj\main.o” --library_type=microlib --strict --scatter “.\Obj\scan.sct” --summary_stderr --info summarysizes --map --xref --callgraph --symbols --info sizes --info totals --info unused --info veneers --list “.\List\scan.map” -o .\Obj\scan.axf

2.2.2. makefile内容

APP ?= .\output\scan.afx
MACRO ?=invlid_macro

OUTPUTCHAN ?= SEMIHOSTED

SHELL=$(windir)\system32\cmd.exe
RM_FILES = $(foreach file,$(1),if exist $(file) del /q $(file))
RM_DIRS = $(foreach dir,$(1),if exist $(dir) rmdir /s /q $(dir)$(EOL))

ifeq ($(QUIET),@)
PROGRESS = @echo Compiling $<...
endif

SRC_DIR = Src
ASM_DIR = .
INC_DIR = include
OBJ_DIR = obj
LINK = armlink
OUTPUT_DIR=output

INCLUDES := -I$(INC_DIR)
INCLUDES += -I C:\Keil\ARM\RV31\INC
INCLUDES += -I C:\Keil\ARM\CMSIS\Include

ARCH               := Cortex-M0
CPU                := ARM
LINK_GCC            := link.ld
GCC_LINKER_FILE       := -T link/$(LINK_GCC)

GCC_TOOLCHAIN   := C:\Keil\ARM\ARMCC\bin
ASM         := $(GCC_TOOLCHAIN)\ArmAsm
CC        := $(GCC_TOOLCHAIN)\ArmCC
LINKER_CSRC := $(GCC_TOOLCHAIN)\armlink
FROMELF := $(GCC_TOOLCHAIN)\fromelf

# GCC options
ASM_OPTS          := --cpu Cortex-M0 -g --apcs=interwork --pd "__MICROLIB SETA 1"
CC_OPTS           := --cpu Cortex-M0 -D__MICROLIB -g -O2 -Otime --apcs=interwork -D$(MACRO)
LINKER_FILE		  := --cpu Cortex-M0 --library_type=microlib --strict --scatter ".\scan.sct"  --info summarysizes 


APP_C_SRC := $(wildcard $(SRC_DIR)/*.c)
APP_S_SRC := $(wildcard $(ASM_DIR)/*.s)
OBJ_FILES := $(APP_C_SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) 
OBJ_FILES += $(APP_S_SRC:$(ASM_DIR)/%.s=$(OBJ_DIR)/%.o)
DIRS= $(OUTPUT_DIR) $(OBJ_DIR) 

.phony: all clean

all: FORCE $(APP)

FORCE:
	@echo being

$(APP): $(DIRS) $(OBJ_FILES) 
	@echo Linking $@
	$(LINKER_CSRC) $(LINKER_FILE) $(OBJ_FILES) -o $@
	$(FROMELF) $@ --i32combined --output ".\output\scan.hex"
	$(FROMELF) $@  --bin --output ".\output\scan.bin"
	@echo Done.

clean:
	$(call RM_DIRS,$(OBJ_DIR))
	$(call RM_DIRS,$(OUTPUT_DIR))
	$(call RM_FILES,$(APP))


$(DIRS):
	mkdir $@
	@echo $(OBJ_FILES)

$(OBJ_DIR)/%.o : $(SRC_DIR)/%.c
	@echo begin to comple c source
	$(CC) -c $(CC_OPTS) -o $@ $<

$(OBJ_DIR)/%.o : $(ASM_DIR)/%.s
	@echo begin to comple asm
	$(ASM) -c $(ASM_OPTS)  -o $@ $<

# Make sure everything is rebuilt if this makefile is changed
$(OBJ_FILES) $(APP): makefile

2.2.3. 多预编译宏

利用批处理,添加MACRO参数控制预编译宏。

echo off
set list=__AAA_ __BBB__  __CCCC__ __DDDD__

for %%n in (%list%) do (
	make clean
	make MACRO=%%n ) 

3. 其他

3.1. 总结

命令行的方式使用简单,但是灵活度不够,例如一个Target不能指定宏。Makefile比较灵活,但是前期构建工程会比较复杂,但是如果makefile构建完成之后,使用也非常方便。

3.2. 参考

  1. https://developer.arm.com/documentation/101407/0537/Command-Line

你可能感兴趣的:(嵌入式,Keil,makefile)