如果您的数组是只读的,则无需将其复制到 RAM。你可以
将其保存在 Flash 中,并在需要时从那里读取。这将拯救你
宝贵的 RAM,代价是访问速度较慢(从 RAM 读取需要 2 个周期,
从闪存读取是 3 个周期)。
你可以这样声明你的数组:
.global my_array
.type my_array, @object
my_array:
.byte 12, 34, 56, 78
然后,要读取数组的成员,您必须计算:
adress of member = array base address + member index
如果您的成员超过一个字节,您还必须乘以
索引的大小,但这里不是这种情况。然后,你把
Z 寄存器中所需成员的地址并发出lpm
操作说明。下面是一个实现这个逻辑的函数:
.global read_data
; input: r24 = array index, r1 = 0
; output: r24 = array value
; clobbers: r30, r31
read_data:
ldi r30, lo8(my_array) ; load Z = address of my_array
ldi r31, hi8(my_array) ; ...high byte also
add r30, r24 ; add the array index
adc r31, r1 ; ...and add 0 to propagate the carry
lpm r24, Z
ret
@scottt 建议你先用 C 写,然后看看生成的
部件。我认为这个建议非常好,让我们遵循它:
#include <stdint.h>
__flash const uint8_t my_array[] = {12, 34, 56, 78};
uint8_t read_data(uint8_t index)
{
return my_array[index];
}
标识“命名地址空间”的 __flash 关键字是嵌入的
C 扩展名supported by
gcc。这
生成的程序集与前一个程序集略有不同:相反
计算base_address + index,gcc 做index − (−base_address):
read_data:
mov r30, r24 ; load Z = array index
ldi r31, 0 ; ...high byte of index is 0
subi r30, lo8(-(my_array)) ; subtract -(address of my array)
sbci r31, hi8(-(my_array)) ; ...high byte also
lpm r24, Z
ret
这与之前的手卷组件一样高效,除了
它不需要将 r1 寄存器初始化为零。但
无论如何,将 r1 保持为零是 gcc ABI 的一部分,所以它应该不会
区别。
链接器的作用
本部分旨在回答评论中的问题:我们如何才能
如果我们不知道它的地址,访问数组?答案是:我们访问
它的名字,就像上面的代码 sn-ps 一样。选择决赛
数组的地址,以及用适当的名称替换名称
地址,是链接器的工作。
组装(avr-gcc -c)和拆卸(avr-objdump -d)
第一个代码 sn-p 给出了这个:
my_array.o, section .text:
00000000 <my_array>:
0: 0c 22 38 4e ."8N
如果我们从 C 编译,gcc 会将数组放在
.progmem.data 部分而不是 .text,但差别不大。
数字“0c 22 38 4e”是数组内容,以十六进制表示。那些角色
右边是 ASCII 等价物,“.”是占位符
非打印字符。
目标文件也带有这个符号表,由avr-nm表示:
my_array.o:
00000000 T my_array
表示符号“my_array”已被定义为引用偏移量 0
进入此对象的 .text 部分(由“T”表示)。
汇编和反汇编第二个代码 sn -p 给出:
read_data.o, section .text:
00000000 <read_data>:
0: e0 e0 ldi r30, 0x00
2: f0 e0 ldi r31, 0x00
4: e8 0f add r30, r24
6: f1 1d adc r31, r1
8: 84 91 lpm r24, Z
a: 08 95 ret
反汇编与实际源码对比,可以看出
汇编器将 my_array 的地址替换为 0x00,即
几乎肯定是错的。但它也给链接器留下了一个注释
“搬迁记录”的形式,显示为avr-objdump -r:
read_data.o, RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000000 R_AVR_LO8_LDI my_array
00000002 R_AVR_HI8_LDI my_array
这告诉链接器 ldi 指令在偏移量 0x00 和
0x02 用于加载低字节和高字节(分别)
my_array 的最终地址。目标文件也带有这个
符号表:
read_data.o:
U my_array
00000000 T read_data
“U”行表示文件使用了一个未定义的符号,名为
“我的数组”。
使用合适的 main() 将这些部分链接在一起,生成一个二进制文件
包含来自 avr-lbc 的 C 运行时以及我们的代码:
0000003c <my_array>:
3c: 0c 22 38 4e ."8N
00000040 <read_data>:
40: ec e3 ldi r30, 0x3C
42: f0 e0 ldi r31, 0x00
44: e8 0f add r30, r24
46: f1 1d adc r31, r1
48: 84 91 lpm r24, Z
4a: 08 95 ret
需要注意的是,链接器不仅移动了片段
到他们的最终地址,它还修复了ldi 的参数
指令,以便它们现在指向 my_array 的正确地址。