arm ds软件作为arm公司发布的ADS、DS5软件的延续,具备前两款软件的所有功能。同时将Keil MDK单片机开发软件的功能直接整合到了arm ds软件中。现在arm ds就相当于DS5+MDK两款软件的集合体。优点:能够快速开发Cortex-M单片机程序,同时具备开发高端Cortex-A/R系列芯片的能力。
说明:以下裸机程序是运行在开发板进入Uboot的状态下,并非完全的裸机。arm ds软件是windows端的、使用了arm官网的JTAG调试器——DSTREAM
下载arm ds软件,可以从www.arm.com官网下载。也可以从以下百度云盘中下载,试用申请需要注册arm账户。
链接:https://pan.baidu.com/s/1Ww5mWiN9sWObYptJesC7Xw
提取码:jcmu
工程实例:https://pan.baidu.com/s/1vd1nCuGxwvggMKCkdFUWTg
提取码:8xy1
点击File >> New >> Project
编译器选择Arm Compiler 5,如果你开发的芯片为arm 64位的。请选择arm compiler 6
点击Finish完成工程创建。
1. 裸机程序如何运行C代码?
这里采用C语言来实现裸机LED的控制。但是有一个疑问C代码运行需要有一个运行环境——必须有一个栈。不然C代码中使用的变量无法使用。同时芯片启动后这个C代码运行环境需要使用汇编来设置。
2. 为什么不全部采用汇编来编写?
因为汇编属于一种低级语言学习起来比较麻烦,需要记的东西比较多。而且对比较复杂功能的实现会异常麻烦而且不好维护与移植。现代芯片的开发大部都是采用汇编+C语言来开发的,因为C语言的移植性好同时效率也高。
总合上述原因:这里采用汇编+C语言的模式来开发,汇编完成C语言运行环境的设置(常说的启动代码)、C语言完成具体的功能实现。
添加两个文件:start.s、main.c
start.s中设置栈给C代码运行提供环境支持。以下start.s文件中代码
完整代码:
PRESERVE8
AREA Led_4412, CODE, READONLY
ENTRY
;关闭开门狗
LDR r0, =0x10060000
MOV r1, #0
STR r1, [r0]
LDR sp,=0x02050000 ;设置C语言程序的栈地址为iRAM空间
IMPORT main
BL main
B . ;死循环
END
注意:arm工具中的汇编代码与gun arm汇编风格不一样,两种不通用。这里只简单介绍下arm工具中的汇编
分析以上代码:
PRESERVE8:表示8字节对齐,这个是固定的不加编译器会报错。想要详细了解的请百度或者查看arm ds帮助文档arm汇编章节
AREA Led_4412, CODE, READONLY:这个也是固定格式。其中led_4412为代码段名称可以自定义,这里因为是4412开发板上的LED程序所以使用了Led_4412
ENTRY:这个程序入口必须要有表示此汇编程序从这里开始运行。工程中至少有一个ENTRY
这段代码表示关闭开门狗,防止在运行程序时被自动复位了。0x10060000为Exynos 4412芯片的开门狗控制寄存器地址,具体可以查看4412数据手册。
LDR r0, =0x10060000
MOV r1, #0
STR r1, [r0]
LDR:从内存中将数据加载到寄存器中
MOV:将立即数存入寄存器中。
STR:将寄存器中的数据写入内存中
这段是关键代码,设置了栈地址以及从汇编跳转到C代码。其中0x02050000为iRAM空间地址,这个可以从4412手册Memory Map章节查看到。
LDR sp,=0x02050000 ;设置C语言程序的栈地址为iRAM空间
IMPORT main
BL main
IMPORT:表示后面的符号来自外部,这里只的就是C代码。符号名称可以随便修改,但是一定要和C代码中的函数名称对应上。
BL:跳转指令
芯片内存地址结构(非常重要):
接下来编写C代码,这是一个LED流水灯程序
//获取寄存器地址中的内容
#define GPK1CON (*(volatile unsigned long *)0x11000060)
#define GPL2CON (*(volatile unsigned long *)0x11000100)
#define GPK1DAT (*(volatile unsigned long *)0x11000064)
#define GPL2DAT (*(volatile unsigned long *)0x11000104)
void delay(int r0)
{
volatile int count = r0;
while (count--)
;
}
int main(void)
{
GPL2CON = 0x00000001;
GPK1CON = 0x00000010;
while(1)
{
GPL2DAT = 0;
GPK1DAT = 0x02;
delay(0x80000);
GPL2DAT = 1;
GPK1DAT = 0x0;
delay(0x80000);
}
}
接下来我们可以试着编译下
到这里还没有完,因为这些代码放置在哪里运行呢?这里就引出了另外一个“大侠”scatter文件——分散加载文件。这个文件与linux中的链接脚本文件功能一样。只不过scatter不是在linux上的gnu风格的。具体详细的scatter文件如何使用,编写可以百度或者arm ds的帮助文档。
使用scatter文件告诉armlinker如何将这些代码放置在哪里?因为嵌入式系统中使用的存储器种类有很多,这个文件就是指导链接器完成代码的链接工作。
创建编写scatter文件
选中刚才新建的工程 右键 》》New 》》Other选择scatter File
因为开发板已经加载运行了uboot,所以我们现在可以使用DRAM空间(3G)。具体详细的scatter文件语法等请百度或者查看arm ds的帮助文档scatter章节
;Exynos 4412芯片的内存分布
;执行域中的内容所占地址是依次排列的。
LOAD_REGION 0x40008000 0xC800 ;设置用于保存整个程序所用的空间:50K
{
EXE_REGION 0x40008000 0xC800 ;根域:加载域==执行域
{
start.o ;start.S启动文件中的代码先运行
*(+RO) ;接着是其他代码
}
; 这个区域地址可以
STACK_REGION 0x02050000 0x0400 ;设置C代码中栈大小为1K
{
*(+RW,+ZI) ;程序中使用到的变量
}
}
注意:一个scatter文件中必须至少有一个固定域。这个固定域用于保存镜像的初始入口。任何一个映像文件都需要指定一个初始入口点(initial entry point),它是影响文件运行时的入口点
加载域:代码存放的地址。就是编译之后得到的二进制文件烧写到rom中的这一段区域,所有的代码RO、预定义变量RW、堆栈之类清不清空无关紧要的大片内存区域ZI,都包括在其中
执行域:代码中变量数据存放的地址。就是把加载域进行‘解压缩’后的样子。比如:RO没有变动还是在ROM中,RW被移到了SRAM中,而ZI被放置在SDRAM中
固定域:所谓固定域是指该域的加载时地址和运行时地址是相同的
scatter文件格式,就是
加载域
{
执行域1
{
}
执行域2
{
}
}
上述代码分析:
LOAD_REGION 0x40008000 0xC800:设置了加载域地址为0x40008000 ,这个地址可以从4412手册memory map章节查看到。0xC800为这个加载域的大小。LOAD_REGION是这个加载域的名称可以随便取。
注意:加载域与执行域没有包含关系,不要被代码格式所欺骗。想当然的认为和C语言中”{}“一样
到这里整个工程就完整了,接下来就是要设置工程属性中的Image layout
选中工程右键选择Properties 》》 C/C++ Build 》》Settings 》》Arm Linker 5 》》Image layout
设置镜像入口地址:0x40008000这个得与scatter文件中的固定域一致。
添加Scatter文件的路径。
好了到了这里,我们终于把工程的配置设置完成了。执行编译操作,查看是否有错误
没有问题后,我们开始调试
选中工程右键 》》 Debug As 选择对应的芯片
注意:如果在芯片列表中没有找到对应芯片需要自行创建芯片数据库。请参考下一章节
这里选择Exynos 44XX >> Bare Metal Debug >> Debug Cortex-A9_0。表示在芯片的第一个A9内核上调试。
点击Browse选择DSTREAM调试器
在File标签中,选择编译生成的axf文件
在Debugger标签中选择Debug from symbo main
最后点击Debug,等待DSTREAM连接上目标设备。连接成功后软件自动会停止在main函数处
点击运行程序,此时可以看到开发板上LED灯来回闪烁
点击File >> New >> Other...
选择Platform Configuration
选择自动获取芯片数据
然后一步一步按照向导完成芯片数据库的创建即可