问题解答:
1 概述
keil是业界最好的51单片机开发工具之一,它拥有流畅的用户界面与强大的仿真功能。arm将keil公司收购之后,正式推出了针对ar
问题解答:
1 概述
keil是业界最好的51单片机开发工具之一,它拥有流畅的用户界面与强大的仿真功能。arm将keil公司收购之后,正式推出了针对arm微控制器的开发工具rvmdk,它将arm编译器rvct与keil的工程管理、调试仿真工具集成在一起,是一款非常强大的arm微控制器开发工具。2007年5月,arm正式授权中国深圳英蓓特公司代理中文版rvmdk的出售事务。
很多嵌入式系统开发工程师对arm的老版本开发工具ads1.2非常熟悉,而rvmdk与ads相比较,从外观、仿真流程以及内部二进制编译链接工具上都有了不少改进,用法稍有不同。本主的主旨是介绍通用的流程,以及一些注意事项,帮助ads1.2用户将老的,遗留的ads1.2工程转化成在rvmdk上进行开发调试的工程。
2 工具结构的改进
arm新推出的微控制器开发工具rvmdk与ads1.2在工具架构组成上有一些不同,这些区别包括:不同版本的arm编译器(compiler),不同的调试器(debugger),不同的仿真器(simulator),以及不同的硬件调试单元。作为arm的新一代微控制器开发工具,rvmdk不但包含arm的最新版本编译链接工具,即rvds3.0的编译链接工具,而且根据微控制器调试开发的特点采用了与ads,rvds完全不同的调试、仿真环境,uvision debugger 与simulator。
rvmdk集成了rvds3.0的编译工具rvct3.0,与ads1.2相比,除去编译、连接工具的可执行二进制文件不同之外,rvct3.0的很多编译连接选项与ads编译器也有不同。
2.1 posix格式
rvct3.0采用了posix格式的编译连接选项,所有的多字符选项前必须使用双下划线。例如:ads的编译选项-cpu,在rvmdk中需要改写成--cpu,否则用户在rvmdk中直接使用ads的makefile时,工具会产生一个如下警告:
warning: l3910w: old syntax, please use ‘--xxx’
2.2 编译器例化形式
在ads中,当用户需要将高级语言代码编译成目标文件时,需要根据目标机器码的不同(16位的thumb代码或者32位的arm代码),以及高级语言的不同(c代码或者c 代码)选择不同的编译器可执行文件;rvct3.0编译器则将它们全部统一为armcc,仅仅通过不同的编译选项进行区分。表1较为详细的罗列了其中的差别。
表1 rvmdk与ads编译器的例化形式对比
ads1.2 rvmdk3.0 默认的编译选项
armcc armcc --c90 –arm
tcc armcc –thumb --c90
armcpp armcc --cpp --arm
tcpp armcc --thumb --cpp
注 表1中“默认的编译选项”是指在没有其它编译选项时指编译器的缺省选项。
2.3 连接器的使用
对目标文件进行链接之前,arm工具的连接器会严格检查各个文件(objects),判断它们是否复合arm体系结构的abi表准。由于rvct与ads编译链接工具所遵循的arm abi不同,所以将ads的遗留工程直接移植到rvmdk并进行连接时,用户可能会遇到如下的错误或者警告:
error: l6238e: foo.o(.text) contains invalid call from '~pres8' function to 'req8' function
warning: l6306w: '~pres8' section foo.o(.text) should not use the address of 'req8' function foobar
这是因为新工具的abi要求在函数调用时,系统必须保证堆栈指针8byte对齐,即每次进栈或者出栈的寄存器数目必须为偶数。这是为了能够更加高效的使用stm与ldr指令对“double”或者“long long”类型的数据进行访问。而老的arm开发工具ads并没有考虑到新的arm内核架构,其abi对于堆栈的操作仅仅要求4byte对齐。所以当用户将在ads中编译连接成功的工程代码移植到rvmdk上,或者将老的、ads遗留的目标文件、库文件在新工具rvmdk中进行连接时,rvmdk的连接器就会报出以上的错误。
对于以上情况,用户可以通过简单修改代码并重新编译链接,或者使用特殊的编译选项来解决。
2.3.1 重新编译所有代码
当用户拥有该ads遗留工程的所有源代码时,使用rvmdk重新编译链接全部代码是最好的解决方法。rvmdk中的新版本编译工具会重新生成满足堆栈8byte对齐要求的目标文件,避免由于堆栈不对齐引起的连接错误。
当工程中包含汇编代码时,用户可能还需要做少量的代码修改。这些修改包括:
1) 检查汇编源码中的指令,确保堆栈操作指令是8byte对其的。
如ex1中,ads的遗留代码一次性将5个寄存器压栈,由于arm的指令寄存器宽度为32位,即4byte,显然5个寄存器入栈之后,堆栈指针不能够满足64位,8byte对齐。为了解决这种情况,我们可以将另外一个并不需要压栈的寄存器,r12,同时压栈,这样当6个32位寄存器进栈之后,堆栈就能满足64位对齐了。
ex.1
stmfd sp!, {r0-r3, lr} ; 将r0,r1,r2,r3,lr(奇数)寄存器入栈
ê
stmfd sp!, {r0-r3, r12, lr} ; 将偶数个寄存器入栈
2) 在每个汇编文件的开头,添加“preserve8”指令。见ex2。
ex.2
area init, code, readonly
ê
preserve8
area init, code, readonly
2.3.2 使用--apcs /adsabi编译选项
当用户没有该ads遗留工程的全部源码,只拥有库文件或者目标文件时,可以通过--apcs/adsabi编译选项强制rvmdk的编译器产生复合ads abi要求的目标文件,以达到与遗留的ads库文件、目标文件兼容的目的。
注 arm新工具将不会继续支持--apcs/adsabi选项。建议用户及时更新工具到最新版本。
2.4 分散加载文件
rvmdk同样支持ads的分散加载文件,但是当分散加载文件中涉及到必须被放置root region中的c库函数时,有时用户需要作少量修改。
root region的load address与execution address相同,所以这部分代码在系统初始化时无需进行搬移操作,很多库函数,如__scatter*.o或者__dc*.o,必须被放置在root region中。
ex.3 – 分散加载文件的修改
; ads 中的分散加载文件
rom_load 0x0
{
rom_exec 0x0
{ vectors.o (vect, first)
__main.o ( ro)
* (region$$table)
* (zisection$$table)
}
ram_exec 0x100000
{ *.o ( ro, rw, zi) }
}
ê
; rvmdk中的分散加载文件1 ; rvmdk中的分散加载文件2
rom_load 0x0 rom_load 0x0
{ {
rom_exec 0x0 rom_exec 0x0
{ {
vectors.o (vect, first) vectors.o (vect, first)
* (inroot$$sections) __main.o(*)
} * (region$$table)
ram_exec 0x100000 __scatter*.o(*)
{ __dc*.o(*)
*.o ( ro, rw, zi) }
} ram_exec 0x100000
} { *.o ( ro, rw, zi)}
}
在ads中,用户必须在分散加载文件中明确的将特定section代码放置在root region中。而rvmdk为了支持新的rw压缩机制,采用了新的region table格式,这种新的格式并不包含zisection$$table,而且新的scatter-loading (__scatter*.o) 与 decompressor (__dc*.o)必须被放置在root region中。所以ex3中ads的分散加载文件应该被修改成新的形式。例3中提供了两种修改分散加载文件的方法,分散加载文件1通过inroot$$sections自动将所有必须的库目标放至在root region中,而分散加载文件2则详细的注明了__scatter*.o与 __dc*.o的位置。
2.5 c库函数的差异
为了与新的abi一致,rvmdk中的库函数名称与ads可能会有不同。ads中的__rt_*库函数被替换为__aeabi_*。如果用户的ads工程中曾经修改(retarget)过这些库函数,那么在移植到rvmdk时,需要重新实现这些函数,以满足新abi的要求。表2列出了部分函数的对应关系。
表2 部分库函数对比
ads库函数 rvmdk库函数
__rt_memcpy_w __aeabi_memcpy4
__rt_div0 __aeabi_idiv0
__rt_sdiv __aeabi_idiv
__rt_udiv __aeabi_uidiv
__rt_fp_status_addr __aeabi_fp_status_addr
__rt_errno_addr __aeabi_errno_addr
3. 移植实例
结合以上对rvmdk与ads差异的描述,本小节将以实例的形式叙述如何将ads1.2上的遗留代码移植到rvmdk上。
以philip的lpc2294(arm7tdmi)为处理器,将一个在ads1.2上开发的由lpc2294控制led闪烁的工程移植到rvmdk上来。由图1可以看出,该工程(legacy_ads.mcp)共有4个源文件(startup.s、tartget.c、irq.s、main.c),以及一个分散加载文件(scatterload)。
图1 ads遗留工程
使用ads1.2编译器,
编译选项为:-o1 –g
链接选项为:-info totals -entry 0x00000000 -scatter .\src\scatterload.scf -info sizes
我们得到最终代码尺寸信息如下:
total ro size(code ro data) 1640 ( 1.60kb)
total rw size(rw data zi data) 1128 ( 1.10kb)
total rom size(code ro data rw data) 1640 ( 1.60kb)
为了能够使用arm新工具rvmdk的一系列特性,我们需要把ads中的遗留工程移植到rvmdk上来。其具体步骤如下:
1) 在rvmdk中新建工程
打开rvmdk,在主菜单中选择projectànew…àuvision project,并给新工程命名为new_mdk.uv2,单击“保存”,见图2。
图2 在rvmdk中新建工程
在rvmdk自动弹出的器件选择窗口(select device for target)中选择该工程所对应的处理器型号,“lpc2294”,并单击“确定”,见图3。当rvmdk提示用户是否自动添加启动代码时,选择“否”。
图3 在rvmdk中选择合适的处理器
2) 添加源文件,并设置工程属性
将legacy_ads.mcp工程中所有的源文件都添加到新的new_mdk.uv2工程中来,见图4。
图4 将ads工程的遗留源代码全部添加到新工程中
单击工程属性快捷键 ,打开工程属性设置窗口,并选择c/c 标签页,设置编译器属性。用户可以根据以前ads工程的编译属性设置,也可以根据当前具体需求重新设置编译属性。在本例中,我们将ads遗留工程的编译属性,“-o1 -g ”修改为“-o1 -g -w”后,拷贝到“misc controls”栏中来,见图5。这是因为由于编译器版本的变化,其对应的编译选项也有所变化的缘故。
注 -w选项可以抑止所有的warning。
图5 编译选项的设置
图6 链接选项的设置
对ads工程中的链接选项作适当修改如下,使其复合posix格式。
--info totals --entry 0x00000000 --scatter .\src\scatterload.scf --info sizes
选择linker标签,将修改过的链接选项拷贝至rvmdk工程属性的linker属性中,并单击“确定”,见图6。
3)build工程并适当修改代码
当所有的工程属性都设置好之后,单击“build all target file”快捷键 ,对整个工程进行编译链接。在rvmdk窗口的build输出一栏中,我们会发现系统出现了一个链接错误l6238e,见图7,这是由于rvmdk中新版本编译链接工具与ads的老版本build工具采用不同的abi造成的(详见本文2.3小节)。
图7 链接错误l6238e
为此我们打开该工程中的汇编文件startup.s,在该程序第55行添加preserve8指令,如下所示:
code32
preserve8
area vectors,code,readonly
4) 重新编译链接该工程
代码修改完毕之后,单击“build all target file”快捷键 ,对该工程进行二次编译连接。rvmdk将成功生成new_mdk.axf文件,并显示其代码尺寸信息为:
program size: code=1576 ro-data=64 rw-data=0 zi-data=1128
5) 工程调试
与其它arm开发工具相比较,rvmdk拥有非常出色的仿真功能,可以帮助用户在纯软件的平台上进行较为精确的调试。用户可以在工程属性设置窗口选择simulator调试(见图8)或者通过硬件调试工具(ulink)进行调试。
图8 选择uvision simulator作为调试平台
当选择simulator调试时,单击debug快捷键 ,打开simulator调试窗口,见图9。为了验证该程序在lpc2294硬件平台上是否能够正确执行,通过gpio口驱动led进行循环闪烁,用户可以单击peripherals->gpio->port2,将gpio端口2的仿真界面打开,见图9。
图9 rvdmk调试环境
单击运行快捷键 ,可以看到在gpio端口2的仿真调试窗口中,io口的输出在不停的循环变化。
m微控制器的开发工具rvmdk,它将arm编译器rvct与keil的工程管理、调试仿真工具集成在一起,是一款非常强大的arm微控制器开发工具。2007年5月,arm正式授权中国深圳英蓓特公司代理中文版rvmdk的出售事务。
很多嵌入式系统开发工程师对arm的老版本开发工具ads1.2非常熟悉,而rvmdk与ads相比较,从外观、仿真流程以及内部二进制编译链接工具上都有了不少改进,用法稍有不同。本主的主旨是介绍通用的流程,以及一些注意事项,帮助ads1.2用户将老的,遗留的ads1.2工程转化成在rvmdk上进行开发调试的工程。
2 工具结构的改进
arm新推出的微控制器开发工具rvmdk与ads1.2在工具架构组成上有一些不同,这些区别包括:不同版本的arm编译器(compiler),不同的调试器(debugger),不同的仿真器(simulator),以及不同的硬件调试单元。作为arm的新一代微控制器开发工具,rvmdk不但包含arm的最新版本编译链接工具,即rvds3.0的编译链接工具,而且根据微控制器调试开发的特点采用了与ads,rvds完全不同的调试、仿真环境,uvision debugger 与simulator。
rvmdk集成了rvds3.0的编译工具rvct3.0,与ads1.2相比,除去编译、连接工具的可执行二进制文件不同之外,rvct3.0的很多编译连接选项与ads编译器也有不同。
2.1 posix格式
rvct3.0采用了posix格式的编译连接选项,所有的多字符选项前必须使用双下划线。例如:ads的编译选项-cpu,在rvmdk中需要改写成--cpu,否则用户在rvmdk中直接使用ads的makefile时,工具会产生一个如下警告:
warning: l3910w: old syntax, please use ‘--xxx’
2.2 编译器例化形式
在ads中,当用户需要将高级语言代码编译成目标文件时,需要根据目标机器码的不同(16位的thumb代码或者32位的arm代码),以及高级语言的不同(c代码或者c 代码)选择不同的编译器可执行文件;rvct3.0编译器则将它们全部统一为armcc,仅仅通过不同的编译选项进行区分。表1较为详细的罗列了其中的差别。
表1 rvmdk与ads编译器的例化形式对比
ads1.2 rvmdk3.0 默认的编译选项
armcc armcc --c90 –arm
tcc armcc –thumb --c90
armcpp armcc --cpp --arm
tcpp armcc --thumb --cpp
注 表1中“默认的编译选项”是指在没有其它编译选项时指编译器的缺省选项。
2.3 连接器的使用
对目标文件进行链接之前,arm工具的连接器会严格检查各个文件(objects),判断它们是否复合arm体系结构的abi表准。由于rvct与ads编译链接工具所遵循的arm abi不同,所以将ads的遗留工程直接移植到rvmdk并进行连接时,用户可能会遇到如下的错误或者警告:
error: l6238e: foo.o(.text) contains invalid call from '~pres8' function to 'req8' function
warning: l6306w: '~pres8' section foo.o(.text) should not use the address of 'req8' function foobar
这是因为新工具的abi要求在函数调用时,系统必须保证堆栈指针8byte对齐,即每次进栈或者出栈的寄存器数目必须为偶数。这是为了能够更加高效的使用stm与ldr指令对“double”或者“long long”类型的数据进行访问。而老的arm开发工具ads并没有考虑到新的arm内核架构,其abi对于堆栈的操作仅仅要求4byte对齐。所以当用户将在ads中编译连接成功的工程代码移植到rvmdk上,或者将老的、ads遗留的目标文件、库文件在新工具rvmdk中进行连接时,rvmdk的连接器就会报出以上的错误。
对于以上情况,用户可以通过简单修改代码并重新编译链接,或者使用特殊的编译选项来解决。
2.3.1 重新编译所有代码
当用户拥有该ads遗留工程的所有源代码时,使用rvmdk重新编译链接全部代码是最好的解决方法。rvmdk中的新版本编译工具会重新生成满足堆栈8byte对齐要求的目标文件,避免由于堆栈不对齐引起的连接错误。
当工程中包含汇编代码时,用户可能还需要做少量的代码修改。这些修改包括:
1) 检查汇编源码中的指令,确保堆栈操作指令是8byte对其的。
如ex1中,ads的遗留代码一次性将5个寄存器压栈,由于arm的指令寄存器宽度为32位,即4byte,显然5个寄存器入栈之后,堆栈指针不能够满足64位,8byte对齐。为了解决这种情况,我们可以将另外一个并不需要压栈的寄存器,r12,同时压栈,这样当6个32位寄存器进栈之后,堆栈就能满足64位对齐了。
ex.1
stmfd sp!, {r0-r3, lr} ; 将r0,r1,r2,r3,lr(奇数)寄存器入栈
ê
stmfd sp!, {r0-r3, r12, lr} ; 将偶数个寄存器入栈
2) 在每个汇编文件的开头,添加“preserve8”指令。见ex2。
ex.2
area init, code, readonly
ê
preserve8
area init, code, readonly
2.3.2 使用--apcs /adsabi编译选项
当用户没有该ads遗留工程的全部源码,只拥有库文件或者目标文件时,可以通过--apcs/adsabi编译选项强制rvmdk的编译器产生复合ads abi要求的目标文件,以达到与遗留的ads库文件、目标文件兼容的目的。
注 arm新工具将不会继续支持--apcs/adsabi选项。建议用户及时更新工具到最新版本。
2.4 分散加载文件
rvmdk同样支持ads的分散加载文件,但是当分散加载文件中涉及到必须被放置root region中的c库函数时,有时用户需要作少量修改。
root region的load address与execution address相同,所以这部分代码在系统初始化时无需进行搬移操作,很多库函数,如__scatter*.o或者__dc*.o,必须被放置在root region中。
ex.3 – 分散加载文件的修改
; ads 中的分散加载文件
rom_load 0x0
{
rom_exec 0x0
{ vectors.o (vect, first)
__main.o ( ro)
* (region$$table)
* (zisection$$table)
}
ram_exec 0x100000
{ *.o ( ro, rw, zi) }
}
ê
; rvmdk中的分散加载文件1 ; rvmdk中的分散加载文件2
rom_load 0x0 rom_load 0x0
{ {
rom_exec 0x0 rom_exec 0x0
{ {
vectors.o (vect, first) vectors.o (vect, first)
* (inroot$$sections) __main.o(*)
} * (region$$table)
ram_exec 0x100000 __scatter*.o(*)
{ __dc*.o(*)
*.o ( ro, rw, zi) }
} ram_exec 0x100000
} { *.o ( ro, rw, zi)}
}
在ads中,用户必须在分散加载文件中明确的将特定section代码放置在root region中。而rvmdk为了支持新的rw压缩机制,采用了新的region table格式,这种新的格式并不包含zisection$$table,而且新的scatter-loading (__scatter*.o) 与 decompressor (__dc*.o)必须被放置在root region中。所以ex3中ads的分散加载文件应该被修改成新的形式。例3中提供了两种修改分散加载文件的方法,分散加载文件1通过inroot$$sections自动将所有必须的库目标放至在root region中,而分散加载文件2则详细的注明了__scatter*.o与 __dc*.o的位置。
2.5 c库函数的差异
为了与新的abi一致,rvmdk中的库函数名称与ads可能会有不同。ads中的__rt_*库函数被替换为__aeabi_*。如果用户的ads工程中曾经修改(retarget)过这些库函数,那么在移植到rvmdk时,需要重新实现这些函数,以满足新abi的要求。表2列出了部分函数的对应关系。
表2 部分库函数对比
ads库函数 rvmdk库函数
__rt_memcpy_w __aeabi_memcpy4
__rt_div0 __aeabi_idiv0
__rt_sdiv __aeabi_idiv
__rt_udiv __aeabi_uidiv
__rt_fp_status_addr __aeabi_fp_status_addr
__rt_errno_addr __aeabi_errno_addr
3. 移植实例
结合以上对rvmdk与ads差异的描述,本小节将以实例的形式叙述如何将ads1.2上的遗留代码移植到rvmdk上。
以philip的lpc2294(arm7tdmi)为处理器,将一个在ads1.2上开发的由lpc2294控制led闪烁的工程移植到rvmdk上来。由图1可以看出,该工程(legacy_ads.mcp)共有4个源文件(startup.s、tartget.c、irq.s、main.c),以及一个分散加载文件(scatterload)。
图1 ads遗留工程
使用ads1.2编译器,
编译选项为:-o1 –g
链接选项为:-info totals -entry 0x00000000 -scatter .\src\scatterload.scf -info sizes
我们得到最终代码尺寸信息如下:
total ro size(code ro data) 1640 ( 1.60kb)
total rw size(rw data zi data) 1128 ( 1.10kb)
total rom size(code ro data rw data) 1640 ( 1.60kb)
为了能够使用arm新工具rvmdk的一系列特性,我们需要把ads中的遗留工程移植到rvmdk上来。其具体步骤如下:
1) 在rvmdk中新建工程
打开rvmdk,在主菜单中选择projectànew…àuvision project,并给新工程命名为new_mdk.uv2,单击“保存”,见图2。
图2 在rvmdk中新建工程
在rvmdk自动弹出的器件选择窗口(select device for target)中选择该工程所对应的处理器型号,“lpc2294”,并单击“确定”,见图3。当rvmdk提示用户是否自动添加启动代码时,选择“否”。
图3 在rvmdk中选择合适的处理器
2) 添加源文件,并设置工程属性
将legacy_ads.mcp工程中所有的源文件都添加到新的new_mdk.uv2工程中来,见图4。
图4 将ads工程的遗留源代码全部添加到新工程中
单击工程属性快捷键 ,打开工程属性设置窗口,并选择c/c 标签页,设置编译器属性。用户可以根据以前ads工程的编译属性设置,也可以根据当前具体需求重新设置编译属性。在本例中,我们将ads遗留工程的编译属性,“-o1 -g ”修改为“-o1 -g -w”后,拷贝到“misc controls”栏中来,见图5。这是因为由于编译器版本的变化,其对应的编译选项也有所变化的缘故。
注 -w选项可以抑止所有的warning。
图5 编译选项的设置
图6 链接选项的设置
对ads工程中的链接选项作适当修改如下,使其复合posix格式。
--info totals --entry 0x00000000 --scatter .\src\scatterload.scf --info sizes
选择linker标签,将修改过的链接选项拷贝至rvmdk工程属性的linker属性中,并单击“确定”,见图6。
3)build工程并适当修改代码
当所有的工程属性都设置好之后,单击“build all target file”快捷键 ,对整个工程进行编译链接。在rvmdk窗口的build输出一栏中,我们会发现系统出现了一个链接错误l6238e,见图7,这是由于rvmdk中新版本编译链接工具与ads的老版本build工具采用不同的abi造成的(详见本文2.3小节)。
图7 链接错误l6238e
为此我们打开该工程中的汇编文件startup.s,在该程序第55行添加preserve8指令,如下所示:
code32
preserve8
area vectors,code,readonly
4) 重新编译链接该工程
代码修改完毕之后,单击“build all target file”快捷键 ,对该工程进行二次编译连接。rvmdk将成功生成new_mdk.axf文件,并显示其代码尺寸信息为:
program size: code=1576 ro-data=64 rw-data=0 zi-data=1128
5) 工程调试
与其它arm开发工具相比较,rvmdk拥有非常出色的仿真功能,可以帮助用户在纯软件的平台上进行较为精确的调试。用户可以在工程属性设置窗口选择simulator调试(见图8)或者通过硬件调试工具(ulink)进行调试。
图8 选择uvision simulator作为调试平台
当选择simulator调试时,单击debug快捷键 ,打开simulator调试窗口,见图9。为了验证该程序在lpc2294硬件平台上是否能够正确执行,通过gpio口驱动led进行循环闪烁,用户可以单击peripherals->gpio->port2,将gpio端口2的仿真界面打开,见图9。
图9 rvdmk调试环境
单击运行快捷键 ,可以看到在gpio端口2的仿真调试窗口中,io口的输出在不停的循环变化。