Tiny4412使用C语言实现流水灯的裸机程序
2018-02-18 18:21
302 查看
本文转自:http://www.techbulo.com/1347.html
在前边我们使用汇编完成了一个流水灯实验: https://my.oschina.net/cht2000/blog/1622224
但是,汇编语言可读性太差,在这一节我们用 C语言来实现了同样的功能,而以后的试验也尽量用 C语言实现。
我们在编写上位机程序时,C语言程序执行的第一条指令,并不在main函数中。生成一个 C程序的可执行文件时,编译器通常会在我们的代码中加上几个被称为启动文件的代码—— crtl.o 、crti.o、crtend.o 、crtn.o 等,它们是标准库文件。这些代码设置C程序的堆栈等,然后调用 main 函数。它们依赖于操作系统,在裸板上这些代码无法执行,所以需要自己写一个。
这段代码很简单, 关键指令只有2条。自己编写的 start .S启动文件内容如下:
它在第 4行设置好栈指针后,就可以通过第8行调用C函数 main了-----------C函数执行前,必须设置栈。
问:CPU不是有看门狗嘛?为什么没有看到关看门狗的代码?这样程序能正常运行吗?
答:在文章《Exynos 4412的启动过程分析》中,我们已经介绍过了,在执行我们的程序前,CPU会首先执行iROM中的代码和BL1的代码,在这两部分程序中会关闭看门狗。
其实我们自己关闭看门狗也很简单,只需往寄存器WTCON写入0即可;
问:为什么调用C函数要设置栈?
答:1. 栈的整体作用
保存现场;
传递参数:汇编代码调用C函数时,需传递参数;
保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;
2. 详细解释
1)保存现场:
现场,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈push),等调用函数执行完毕返回后(出栈pop),再恢复现场。这样CPU就可以正确的继续执行了。保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。然后待被调用的子函数执行完毕的时候,再调用pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,那么之前的PC的值是存在lr中的),然后在子程序执行完毕的时候,再把栈中的lr的值pop出来,赋值给PC,这样就实现了子函数的正确的返回。
2)传递参数:
C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不多于4个,就可以通过寄存器r0~r3传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于4个时,寄存器不够用,就得用栈了。
3)临时变量保存在栈中:
包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
现在,我们可以很容易写出控制 LED 的程序了。毕竟是用C语言嘛,相当的灵活。 main函数在main.c文件中,代码如下:
来看看Makefile:
执行 make 命令时,它的目是去生成第1个目标,即 led.bin ;
led.bin 依赖于start.o 和 main.o,所以要先生成这 2个.o 文件;
start.o 依赖于start.S ,符合第 11 行的规则,会使用第 12 行的命令生成start.o ;
类似的, main.o 依赖于main.c ,符合第 8行的规则,会使用第 9行的命令生成 main.o ;
当这 2个.o 文件都生成之后,就会执行第 4~6行的命令生成 led.bin文件: 第 4行将编译得到的 .o 文 件连接为led.elf
可执行程序,第 5行是生成二进制格式的可执行程序,第 6行是得到反汇编程序以供查看。
链接脚本还和汇编流水灯一样。这里不再介绍。
好了,下面开始验证我们的程序了。
1.将程序源码上传到服务器,并执行make,生成led.bin文件。
2.借鉴上一个实验的步骤,将程序烧写到SD卡。
在前边我们使用汇编完成了一个流水灯实验: https://my.oschina.net/cht2000/blog/1622224
但是,汇编语言可读性太差,在这一节我们用 C语言来实现了同样的功能,而以后的试验也尽量用 C语言实现。
我们在编写上位机程序时,C语言程序执行的第一条指令,并不在main函数中。生成一个 C程序的可执行文件时,编译器通常会在我们的代码中加上几个被称为启动文件的代码—— crtl.o 、crti.o、crtend.o 、crtn.o 等,它们是标准库文件。这些代码设置C程序的堆栈等,然后调用 main 函数。它们依赖于操作系统,在裸板上这些代码无法执行,所以需要自己写一个。
这段代码很简单, 关键指令只有2条。自己编写的 start .S启动文件内容如下:
.text .globl _start _start: ldr sp, =0x02060000 // 调用C函数之前必须设置栈,栈用于保存运行环境,给局部变量分配空间 // 参考ROM手册P14, 我们把栈指向BL2上方1K处(1K已经够用), // 即:0x02020000 (iRAM基地址) + 5K(iROM代码用) + 8K(BL1用) + 16K(BL2用) + 1K(用作栈)) bl main // 调用main函数(main这个名称不是固定的,可以随意改) halt_loop: b halt_loop
它在第 4行设置好栈指针后,就可以通过第8行调用C函数 main了-----------C函数执行前,必须设置栈。
问:CPU不是有看门狗嘛?为什么没有看到关看门狗的代码?这样程序能正常运行吗?
答:在文章《Exynos 4412的启动过程分析》中,我们已经介绍过了,在执行我们的程序前,CPU会首先执行iROM中的代码和BL1的代码,在这两部分程序中会关闭看门狗。
其实我们自己关闭看门狗也很简单,只需往寄存器WTCON写入0即可;
问:为什么调用C函数要设置栈?
答:1. 栈的整体作用
保存现场;
传递参数:汇编代码调用C函数时,需传递参数;
保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;
2. 详细解释
1)保存现场:
现场,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈push),等调用函数执行完毕返回后(出栈pop),再恢复现场。这样CPU就可以正确的继续执行了。保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。然后待被调用的子函数执行完毕的时候,再调用pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,那么之前的PC的值是存在lr中的),然后在子程序执行完毕的时候,再把栈中的lr的值pop出来,赋值给PC,这样就实现了子函数的正确的返回。
2)传递参数:
C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不多于4个,就可以通过寄存器r0~r3传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于4个时,寄存器不够用,就得用栈了。
3)临时变量保存在栈中:
包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
现在,我们可以很容易写出控制 LED 的程序了。毕竟是用C语言嘛,相当的灵活。 main函数在main.c文件中,代码如下:
#define GPM4CON (*(volatile unsigned int *)0x110002E0) #define GPM4DAT (*(volatile unsigned int *)0x110002E4) void delay(volatile int time) { for(; time > 0; time-- ); } int main(void) { unsigned long tmp = 0; int i = 0; /* * GPM4_0-GPM4_3 设置为输出功能 */ tmp = GPM4CON; tmp &= ~0xffff; tmp |= 0x1111; GPM4CON = tmp; /* * 实现流水灯 */ while(1) { GPM4DAT = i; if (++i == 16) i = 0; delay(9999999); } return 0; }
来看看Makefile:
objs := start.o main.o led.bin : $(objs) arm-linux-ld -Tled.lds -N -o led.elf $^ arm-linux-objcopy -O binary -S led.elf $@ arm-linux-objdump -D -m arm led.elf > led.dis %.o:%.c arm-linux-gcc -Wall -marm -c -O2 -o $@ $< %.o:%.S arm-linux-gcc -Wall -marm -c -O2 -o $@ $< clean: rm -f *.dis *.bin *.elf *.o
执行 make 命令时,它的目是去生成第1个目标,即 led.bin ;
led.bin 依赖于start.o 和 main.o,所以要先生成这 2个.o 文件;
start.o 依赖于start.S ,符合第 11 行的规则,会使用第 12 行的命令生成start.o ;
类似的, main.o 依赖于main.c ,符合第 8行的规则,会使用第 9行的命令生成 main.o ;
当这 2个.o 文件都生成之后,就会执行第 4~6行的命令生成 led.bin文件: 第 4行将编译得到的 .o 文 件连接为led.elf
可执行程序,第 5行是生成二进制格式的可执行程序,第 6行是得到反汇编程序以供查看。
链接脚本还和汇编流水灯一样。这里不再介绍。
好了,下面开始验证我们的程序了。
1.将程序源码上传到服务器,并执行make,生成led.bin文件。
2.借鉴上一个实验的步骤,将程序烧写到SD卡。
相关文章推荐
- 用Keil-MDK开发TQ2440裸机程序入门教程——LED流水灯实现
- Tiny4412使用汇编点亮LED,实现流水灯效果
- 【C语言】【面试题】使用main函数的参数,实现一个整数计算机,程序可以接受三个参数
- 用Keil-MDK开发TQ2440裸机程序入门教程——LED流水灯实现
- vim使用—实现程序的自动补齐(C语言)
- 【C语言】没事可以试试这个小程序,使用文件操作,模拟实现一个简单的文件拷贝工具!
- 【C语言】【面试题】使用main函数的参数,实现一个整数计算机,程序可以接受三个参数
- 用Keil-MDK开发TQ2440裸机程序入门教程——LED流水灯实现
- 使用C语言实现简单的三子棋程序
- 我的用S3C2410实现 NOR FLASH启动裸机程序 流水灯步骤
- 使用C语言实现简单的扫雷程序
- Eclipse开发调试ARM裸机程序(三)C语言LED_LINUX使用DNW
- C语言 编写程序实现字符串比较,不允许使用strcmp函数。
- tiny4412 裸机程序 四、设置栈和C语言点亮LED
- windows下ping程序使用C语言实现
- C语言实现的RSA算法程序(使用GMP)
- Windows环境是使用C语言计算程序或算法执行时间的不同粒度实现
- vim使用—实现程序的自动补齐(C语言)
- 用Keil-MDK开发TQ2440裸机程序入门教程——LED流水灯实现
- 用C语言实现Ping程序功能