提示与技巧
以下为使用内联汇编程序的示例以及有关解决其局限性的信息。在此文件中,术语”汇编程序函数”是
指在Python中用 @micropython.asm_thumb
装饰器声明的函数,而”子程序”是指从汇编程序函数中调用的汇编程序代码。
1. 代码分支和子程序¶
知道标记相对汇编函数为本地,这一信息十分重要。目前尚无法实现在某函数中定义的子程序从另一个函数中调用。
调用子程序,则发送指令 bl(LABEL)
。这会将控制转移到 label(LABEL)
指令后的指令,并将返回地址存储在链接寄存器( lr
或 r14
)中。
为返回指令,需发送 bx(lr)
,这会使子程序调用后的指令继续执行。这种机制意味着,若子程序要调用另一子程序,则须在调用前保存链接寄存器并在终止前将其恢复。
以下示例对函数调用进行说明。请注意:开始时需分支所有子程序调用:子程序以 bx(lr)
结束执行,而外部函数只是以Python函数样式”下降”结束。
@micropython.asm_thumb
def quad(r0):
b(START)
label(DOUBLE)
add(r0, r0, r0)
bx(lr)
label(START)
bl(DOUBLE)
bl(DOUBLE)
print(quad(10))
以下代码示例演示了嵌套(递归)调用:经典的斐波那契数列。此处,在递归调用前,链接寄存器与其他寄存器一起保存,程序逻辑需保存该寄存器。
@micropython.asm_thumb
def fib(r0):
b(START)
label(DOFIB)
push({r1, r2, lr})
cmp(r0, 1)
ble(FIBDONE)
sub(r0, 1)
mov(r2, r0) # r2 = n -1
bl(DOFIB)
mov(r1, r0) # r1 = fib(n -1)
sub(r0, r2, 1)
bl(DOFIB) # r0 = fib(n -2)
add(r0, r0, r1)
label(FIBDONE)
pop({r1, r2, lr})
bx(lr)
label(START)
bl(DOFIB)
for n in range(10):
print(fib(n))
2. 传输和返回参数¶
本教程详细介绍了汇编程序函数可以支持0到3个参数这一特性,这三个参数须(若使用)命名为 r0
、 r1
和 r2
。执行代码时,寄存器将被初始化为该值。
可用此种方式传输的数据类型为整数和内存地址。使用当前固件,所有可能的32位值都可传输并返回。若返回值可能设置了最高有效位,
则应使用Python类型提示来启用MicroPython以确定值是否应解释为有符号或无符号整数:类型 int
或 uint
。
@micropython.asm_thumb
def uadd(r0, r1) -> uint:
add(r0, r0, r1)
hex(uadd(0x40000000,0x40000000))
将返回0x80000000,证明30位和31位不同的整数的传输和返回。
参数和返回值数量的限制可通过 array
模块方式克服,此方式允许访问任何类型的任何数量的值。
2.1. 多个参数¶
若将一个Python整数数组作为参数传输给汇编函数,则该函数将接收一组连续的整数地址。因此可将多个参数作为单个数组的元素传递。 同样,一个函数可通过将多个值赋值给数组元素来返回多个值。汇编函数尚无法确定数组的长度:这需要传输给函数。
数组的这种用法可进行拓展,以使用三个以上的数组。这是间接完成的: uctypes
模块支持 addressof()
,
其将返回作为参数传递的数组地址。因此,您可使用其他数组的地址填充整数数组:
from uctypes import addressof
@micropython.asm_thumb
def getindirect(r0):
ldr(r0, [r0, 0]) # Address of array loaded from passed array 从传输数组中加载的数组地址
ldr(r0, [r0, 4]) # Return element 1 of indirect array (24) 返回间接数组(24)的元素1
def testindirect():
a = array.array('i',[23, 24])
b = array.array('i',[0,0])
b[0] = addressof(a)
print(getindirect(b))
2.2. 非整数数据类型¶
这些可以通过适当数据类型的数组来处理。例如,可按照如下方法处理单精度浮点数据。这段代码示例需一个浮点数组,并用其平方替换其内容。
from array import array
@micropython.asm_thumb
def square(r0, r1):
label(LOOP)
vldr(s0, [r0, 0])
vmul(s0, s0, s0)
vstr(s0, [r0, 0])
add(r0, 4)
sub(r1, 1)
bgt(LOOP)
a = array('f', (x for x in range(10)))
square(a, len(a))
print(a)
uctypes模块支持使用超出简单数组范围的数据结构。它使Python数据结构能够映射到字节数组实例,然后可将其传输给汇编程序函数。
3. 命名常量¶
通过使用命名常量而非用数字随意命名代码,可以使汇编代码变得更具可读性和可维护性。可通过如下方式实现:
MYDATA = const(33)
@micropython.asm_thumb
def foo():
mov(r0, MYDATA)
const()构造使得MicroPython在编译时用其值替换变量名。若常量在外部Python作用域中声明,则其可在多个汇编函数和Python代码间共享。
4. 汇编代码作为类方法¶
MicroPython将对象实例的地址作为第一个参数传输给类方法。通常,这对汇编函数没有多大用处。通过将函数声明为静态类函数可避免这种情况:
class foo:
@staticmethod
@micropython.asm_thumb
def bar(r0):
add(r0, r0, r0)
5. 使用不支持的指令¶
这些指令可使用数据语句进行编码,如下所示。尽管支持 push()
和 pop()
,以下示例说明其原理。
必要的机器代码可在ARM v7-M体系结构参考手册中查找。请注意:数据调用的第一个参数如
data(2, 0xe92d, 0x0f00) # push r8,r9,r10,r11
表示每个后续参数为2字节值。
6. 克服MicroPython的整数限制¶
Pyboard芯片包含一个CRC发生器。其使用在MicroPython中提出了一个问题,由于返回值覆盖了32位的完整色域, 而MicroPython中的小整数在位30和31中不能存在不同值。使用以下代码可以克服此限制:使用汇编程序将结果放入数组和Python代码中, 以将结果强制转换为任意精度无符号整数。
from array import array
import stm
def enable_crc():
stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x1000
def reset_crc():
stm.mem32[stm.CRC+stm.CRC_CR] = 1
@micropython.asm_thumb
def getval(r0, r1):
movwt(r3, stm.CRC + stm.CRC_DR)
str(r1, [r3, 0])
ldr(r2, [r3, 0])
str(r2, [r0, 0])
def getcrc(value):
a = array('i', [0])
getval(a, value)
return a[0] & 0xffffffff # coerce to arbitrary precision
enable_crc()
reset_crc()
for x in range(20):
print(hex(getcrc(0)))