How to load 32 bit constant from assembler with @micropython.asm_thumb

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
taPIQoLEHUMA
Posts: 15
Joined: Thu Mar 04, 2021 2:59 am

How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by taPIQoLEHUMA » Thu Aug 18, 2022 4:44 pm

I'm playing around with @micropython.asm_thumb just for fun.

I'd like to load a 32bit constant into a register, but haven't found a way to do that yet.

I've also made labels and branches to jump around around data() statements, and haven't been able to load a register with an address relative to the PC.

What HAS worked, is moving PC (r15) to a low register, subtracting 4 two times (because it insists there's no #imm8 with adds or subs), and indirecting fetch twice (first to get the register address, second to read the register).

Here's the other things I've tried, with the errors they gave:

# ldr(r0, [pc, #0]) # thonny doesn't recognize; pico gives invalid syntax
instruction LDR Rd, [Rn, #imm] nope
instruction LDR Rd, [PC, imm8] nope

# ldr(r0, TBASE) # unsupported Thumb instruction 'ldr' with 2 arguments
instruction LDR Rd, label nope

# adr(r0, DATA) # unsupported Thumb instruction 'adr' with 2 arguments
instruction ADR Rd, label nope
Probably because it's an ARM assembler pseudo-instruction?

# add(r0,r0,-8) # 'add' integer 0xfffffff8 doesn't fit in mask
instruction ADDS Rd, RD, #imm8 nope
Even though the document for the instruction says it has an 8-bit immediate field.

It's shorter to pass the address to a little fetcher routine:

Code: Select all

@micropython.asm_thumb
def readreg(r0):
    ldr(r0, [r0,0])
So... is there an easy way to get micropython asm to help me load register addresses easily?

Thanks!

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by pythoncoder » Thu Aug 18, 2022 6:23 pm

The DATA statement is almost unusable. I posted this PR seven years ago to provide a way to actually access the data but it was a bit of a hack and didn't find favour.

The best solution is to put your constants in an array and pass that as an arg (arrays are contiguous). This repo has examples of passing arrays.
Peter Hinch
Index to my micropython libraries.

TheSilverBullet
Posts: 50
Joined: Thu Jul 07, 2022 7:40 am

Re: How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by TheSilverBullet » Fri Aug 19, 2022 8:02 am

Maybe this can be helpful. It builds a table using data entries which can be loaded later-on:

Code: Select all

#!/usr/bin/python3

import micropython
from time import sleep_ms

@micropython.asm_thumb
def test_asm() -> uint:

    align   (4)             # DO NOT MODIFY
    mov     (r7, pc)        #   PC points to the table, so does r7 now
    b       (func_entry)    # DO NOT MODIFY

    # embedded table
    align   (4)
    data    (2, 0x1122)     #  0
    data    (2, 0x3344)     #  2
    data    (2, 0x7788)     #  4
    data    (2, 0x5566)     #  6
    data    (4, 0x3abbccdd) #  8    four byte data (only with two upper bits cleared)
    data    (4, 0x3fffffff) # 12    that's the maximum

    align   (2)
    label   (func_entry)
    # ldrb    (r0, [r7, 0])     # gets 0x22 into r0
    # ldrb    (r0, [r7, 1])     # gets 0x11 into r0
    # ldrh    (r0, [r7, 0])     # gets 0x1122 into r0
    # ldrh    (r0, [r7, 2])     # gets 0x3344 into r0
    # ldr     (r0, [r7, 0])     # gets 0x33441122 into r0
    ldr     (r0, [r7, 4])       # gets 0x55667788 into r0
    # ldr     (r0, [r7, 8])     # gets 0x3abbccdd into r0
    # ldr     (r0, [r7,12])     # gets 0x3fffffff into r0
    # when fuction returns, r0 is the return value

@micropython.asm_thumb
def pico_led(r0) -> uint:

    align   (4)             # DO NOT MODIFY
    mov     (r7, pc)        #   PC (and R7) point to the table
    b       (func_entry)    # DO NOT MODIFY

    # embedded table
    align   (4)
    data    (2, 0x4000)     #  0    Low   IO_BANK0_BASE (0x4001_4000)
    data    (2, 0x4001)     #       High  …
    data    (2, 0x0000)     #  4    OUTPUT LOW
    data    (2, 0x3300)     #  6    OUTPUT HIGH
    data    (1, 0xcc)       #  8    GPIO25CTRL offset
    align   (2)

    label   (func_entry)
    ldr     (r6, [r7, 0])   # loads IO_BANK0_BASE into r6
    ldrb    (r5, [r7, 8])   # loads GPIO25CTRL offset into r5
    add     (r6, r6, r5)    # r6 points to GPIO25CTRL

    ldrh    (r1, [r7, 4])   # loads OUTPUT LOW into r1
    tst     (r0, r0)        # test first call argument
    beq     (L2)
    ldrh    (r1, [r7, 6])   # loads OUTPUT HIGH into r1
    label   (L2)
    str     (r1, [r6, 0])   # write either OUTPUT LOW or HIGH into GPIO25CTRL

rc = test_asm()
print(f'rc = 0x{rc:0x}')

for _ in range(10):
    pico_led(1)
    sleep_ms(100)
    pico_led(0)
    sleep_ms(100)
It would be nice if the data statement could accept either integer variables or pre-defined const()

rkompass
Posts: 66
Joined: Fri Sep 17, 2021 8:25 pm

Re: How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by rkompass » Fri Aug 19, 2022 8:25 am

I successfully used the following to load a 32 bit word in to r5:

Code: Select all

        data(2, 0x4d00)        # ldr  r5, [pc, #0]  load r5 with data following the branch (0x4e00: ldr  r6, [pc, #0])
        b(HERE0)
        data(4, 0x1b8d72e4)    # r5 = 0b00011011100011010111001011100100, the state table in 32 bits
        label(HERE0)
This is the way viper uses (I you are interested in inspecting viper code I can give you an example how to do that).
The ldr command is the data(2,...) instruction. By using other values than 0x4d for the higher byte you load into other registers than r5.
I observed that there is an alignment of this code necessary. I inserted a nop() if necessary, but align() would have been better.
There was a bug that prevented values > 2**30 in the data(4, ...) instruction that was recently removed.

TheSilverBullet
Posts: 50
Joined: Thu Jul 07, 2022 7:40 am

Re: How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by TheSilverBullet » Fri Aug 19, 2022 8:47 am

rkompass wrote:
Fri Aug 19, 2022 8:25 am

Code: Select all

        data(4, 0x1b8d72e4)    # r5 = 0b00011011100011010111001011100100, the state table in 32 bits
There was a bug that prevented values > 2**30 in the data(4, ...) instruction that was recently removed.
Must be very recently.
Unless you can do a data(4, 0xffffffff), then it's not usable for values > 0x3fffffff.
MicroPython v1.19.1 on 2022-07-11; Raspberry Pi Pico with RP2040
still can't do that.
Therefore, splitting an address into two 16bit data values is what works here.

And yes, align(2|4) is the way to go.

samneggs
Posts: 20
Joined: Tue Jun 15, 2021 12:57 am

Re: How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by samneggs » Fri Aug 19, 2022 10:14 pm

Peter's method of passing an array does most of the heavy lifting for me but I also use data directive and even math to come up with constants.
Here's an example of the array method with constants:

Code: Select all

SCRN        = const(0)
GPIO        = const(4)
X_POS       = const(8)
Y_POS       = const(12)
X_VEL       = const(16)
Y_VEL       = const(20)
SPRITES     = const(24)

control = array.array('I',(addressof(screen),0x40014000,100,200,0,1,addressof(sprite_buf))
my_asm(control)

@micropython.asm_thumb
def my_asm(r0):
    ldr(r1,[r0,SCRN])        # screen base address
    ldr(r2,[r0,X_POS])      # player x position
    ldr(r3,[r0,Y_POS])      # player y position
    ldr(r4,[r0,SPRITES])   # sprites base address
When using assembly subroutines sometimes I like not worrying about outside variables, so I'll construct addresses (eg. 0xd000_0060):

Code: Select all

# -------------------divide routine-----r0=r0//r1----uses r0,r1,r6--
    label(DIVIDE)    
    mov(r6,0xd0)
    lsl(r6,r6,24)     # 0d0000000
    add(r6,0x60)      # offset    
    str(r0, [r6, 8])  # SIO_DIV_SDIVIDEND_OFFSET _u(0x00000068)8
    str(r1, [r6, 12]) # SIO_DIV_SDIVISOR_OFFSET _u(0x0000006c)12    
    b(DELAY1)
    label(DELAY1)
    b(DELAY2)
    label(DELAY2)
    b(DELAY3)
    label(DELAY3)
    b(DELAY4)
    label(DELAY4)
    ldr(r1, [r6, 20])  #SIO_DIV_REMAINDER_OFFSET _u(0x00000074)20
    ldr(r0, [r6, 16])  #SIO_DIV_QUOTIENT_OFFSET _u(0x00000070)16
    bx(lr)
    #----------------end divide----------------------------------------  
The data directive is just another way of accessing several variables:

Code: Select all

ROW_ASM    =  const(0)
COL_ASM    =  const(2)
OFFSET_ASM =  const(4)
COLOR_ASM  =  const(6)

@micropython.asm_thumb
def my_asm2(r0):
   bl(DISPLAY_NUMBER)
   b(EXIT)
   
    #--------------------Display Number---display r2---uses r1,r2,r3,r4,r5,r6,r7----------
    label(DISPLAY_NUMBER) 
    mov(r4,pc)             # r4=control data
    b(SKIP2)
    data(2,0,2,0xff00 )  #row,col,offset,color     
    align(2)
    label(SKIP2)
    label(PRINT_LOOP)
    ldrh(r5,[r4,ROW_ASM]) 
    ldrh(r6,[r4,COL_ASM]) 
    #rest of code  
    bx(lr)
    #-------------------End Display Number ------------------
    
    label(EXIT)
    
And my favorite byte swap RGB-BGR conversion: (why isn't this instruction implemented?)

Code: Select all

data(2,0b1011_1010_01_110_110)  # r6=swap color bytes
Lots of copy/paste here so errors abound but you get the drift.
Sam

taPIQoLEHUMA
Posts: 15
Joined: Thu Mar 04, 2021 2:59 am

Re: How to load 32 bit constant from assembler with @micropython.asm_thumb

Post by taPIQoLEHUMA » Tue Aug 23, 2022 11:16 pm

Thank you everyone! Lots of good stuff here. Thanks for helping an old guy learn some new tricks! :-)

Post Reply