====== Lab 06 ====== MicroPython allows programmers to use inline assembler. The inline assembler supports a subset of the ARM Thumb-2 instruction set. The syntax tries to be as close as possible to that defined in the above ARM manual, converted to Python function calls. ===== Lesson objectives ===== * Introduce ARM assembler instructions ===== Documents ===== * [[https://docs.micropython.org/en/latest/reference/asm_thumb2_index.html]] ===== Assembler instructions ===== Instructions operate on 32 bit signed integer data except where stated otherwise. Most supported instructions operate on registers ''R0-R7'' only: where ''R8-R15'' are supported this is stated. Registers R8-R12 must be restored to their initial value before return from a function. Registers ''R13-R15'' constitute the Link Register, Stack Pointer and Program Counter respectively. ==== Register moves ==== Where immediate values are used, these are zero-extended to 32 bits. Thus ''mov(R0, 0xff)'' will set ''R0'' to ''255''. * ''mov(Rd, imm8)'' -> Rd = imm8 * ''mov(Rd, Rn)'' -> Rd = Rn * ''movw(Rd, imm16)'' -> Rd = imm16 * ''movt(Rd, imm16)'' -> Rd = (Rd & 0xffff) | (imm16 << 16) ''movt'' writes an immediate value to the top halfword of the destination register. It does not affect the contents of the bottom halfword. * ''movwt(Rd, imm32)'' -> Rd = imm32 ''movwt'' is a pseudo-instruction: the MicroPython assembler emits a movw followed by a movt to move a 32-bit value into ''Rd''. ==== Register load ==== * ''ldr(Rt, [Rn, imm7])'' -> Rt = [Rn + imm7] Load a 32 bit word * ''ldrb(Rt, [Rn, imm5])'' -> Rt = [Rn + imm5] Load a byte * ''ldrh(Rt, [Rn, imm6])'' -> Rt = [Rn + imm6] Load a 16 bit half word Where a byte or half word is loaded, it is zero-extended to 32 bits. ==== Register Store ==== * ''str(Rt, [Rn, imm7])'' -> [Rn + imm7] = Rt Store a 32 bit word * ''strb(Rt, [Rn, imm5])'' -> [Rn + imm5] = Rt Store a byte (b0-b7) * ''strh(Rt, [Rn, imm6])'' -> [Rn + imm6] = Rt Store a 16 bit half word (b0-b15) ==== Logical instructions ==== * ''and_(Rd, Rn)'' -> Rd &= Rn * ''orr(Rd, Rn)'' -> Rd |= Rn * ''eor(Rd, Rn)'' -> Rd ^= Rn * ''mvn(Rd, Rn)'' -> Rd = Rn ^ 0xffffffff i.e. Rd = 1’s complement of Rn * ''bic(Rd, Rn)'' -> Rd &= ~Rn bit clear Rd using mask in Rn Note the use of ''and_'' instead of ''and'', because ''and'' is a reserved keyword in Python. ==== Shift and rotation instructions ==== * ''lsl(Rd, Rn<0-31>)'' -> Rd <<= Rn * ''lsr(Rd, Rn<1-32>)'' -> Rd = (Rd & 0xffffffff) >> Rn Logical shift right * ''asr(Rd, Rn<1-32>)'' -> Rd >>= Rn arithmetic shift right * ''ror(Rd, Rn<1-31>)'' -> Rd = rotate_right(Rd, Rn) Rd is rotated right Rn bits. A rotation by (for example) three bits works as follows. If Rd initially contains bits ''b31 b30..b0'' after rotation it will contain ''b2 b1 b0 b31 b30..b3'' ==== Branch to label ==== * ''b(LABEL)'' -> Unconditional branch * ''beq(LABEL)'' -> branch if equal * ''bne(LABEL)'' -> branch if not equal * ''bge(LABEL)'' -> branch if greater than or equal * ''bgt(LABEL)'' -> branch if greater than * ''blt(LABEL)'' -> branch if less than (<) (signed) * ''ble(LABEL)'' -> branch if less than or equal to (<=) (signed) * ''bcs(LABEL)'' -> branch if carry flag is set * ''bcc(LABEL)'' -> branch if carry flag is clear * ''bmi(LABEL)'' -> branch if negative * ''bpl(LABEL)'' -> branch if positive * ''bvs(LABEL)'' -> branch if overflow flag set * ''bvc(LABEL)'' -> branch if overflow flag is clear * ''bhi(LABEL)'' -> branch if higher (unsigned) * ''bls(LABEL)'' -> branch if lower or equal (unsigned) ===== Inline assembler functions ===== Inline assembler functions are denoted by a special function decorator. Let’s start with the simplest example: @micropython.asm_thumb def fun(): movw(r0, 42) This function takes no arguments and returns the number ''42''. ''r0'' is a register, and the value in this register when the function returns is the value that is returned. MicroPython always interprets the ''r0'' as an integer, and converts it to an integer object for the caller. If you run ''print(fun())'' you will see it print out ''42''. Inline assembler functions can accept up to 4 arguments. If they are used, they must be named ''r0'', ''r1'', ''r2'' and ''r3'' to reflect the registers and the calling conventions. Here is a function that adds its arguments: @micropython.asm_thumb def asm_add(r0, r1): add(r0, r0, r1) This performs the computation ''r0 = r0 + r1''. Since the result is put in ''r0'', that is what is returned. Try ''asm_add(1, 2)'', it should return ''3''. ===== GPIO manipulation ===== To manipulate with GPIO output we can use [[https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#section_sio|SIO]] memory mapped registers: * [[https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#ioportaddressmap|SIO_BASE]] - ''0xd0000000'' * [[https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-sio-GPIO_OUT|GPIO_OUT]] - offset ''0x010'' * [[https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-sio-GPIO_OUT_SET|GPIO_OUT_SET]] - offset ''0x014'' * [[https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-sio-GPIO_OUT_CLR|GPIO_OUT_CLR]] - offset ''0x018'' Registers ''GPIO_OUT_SET'' and ''GPIO_OUT_CLR'' perform an atomic bit-set and bit-clear respectively.