Supplying Arguments to viper function

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
User avatar
Roberthh
Posts: 2714
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Supplying Arguments to viper function

Post by Roberthh » Sat Feb 13, 2016 8:59 pm

Hello all, is there a method for supplying a bytes data object to a viper function, such that it is seen as a pointer type object. I see how I can declare an int arg, and viper also does not refuse something like arg: ptr8, but call it results in an error: "can't convert bytes to int"

Code: Select all

@micropython.viper        
def data_ptr(data: ptr):
      pass

data_ptr(b"abc")
It seems to me that I have seen something related in the forum, but cannot recall where and when.

Update:
I found that something like:

Code: Select all

@micropython.viper        
def data_ptr(data: ptr):
      p_data = ptr8(data)
      pass

data_ptr(bytearray(b"abc"))
gives no error, but I'm not sure whether it's the right thing, especially whther p_data points at the right data.

Damien
Site Admin
Posts: 642
Joined: Mon Dec 09, 2013 5:02 pm

Re: Supplying Arguments to viper function

Post by Damien » Sat Feb 13, 2016 10:20 pm

You can't pass pointers to bytes objects because bytes are read-only, and there is currently no way to specify constant pointers as types in viper mode.

All pointers in viper are read-write, and so if you pass a buffer object along as an argument then that buffer object must support reading and writing.

I could easily change this behaviour so that the buffer object only needs to be readable. Then it's up to the viper programmer to know if they can store to the pointer, or just load from it. What do you think about that?

Your example with a bytearray works because bytearray's are read/write. BTW, you can specify the data arg to be ptr8 directly in the function definition, no need for an extra cast to ptr8 and the p_data variable (data will point to the bytearray data).

User avatar
Roberthh
Posts: 2714
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Supplying Arguments to viper function

Post by Roberthh » Sun Feb 14, 2016 7:55 am

Hello Damien, thanks for the feedback. I was not sure, and I could not find it written somewhere. I tried it and it seems to work. About your question: For practical purposes it does not matter whether it's possible to specify a pointer as R/O or R/W. The latter works in all circumstances, and you can pass data back to the buffer, even if that might be risky (going into C-land). With a R/O buffer the function call may look a little bit more simple, and you avoid an accidental buffer overwrite.
BTW.
a) Is there a way to determine the size of that buffer object, or do I have to supply a separate argument with the size?
b) What types of data can a viper function return, and how is it specified?
Regards

P.S.: I hope @pythoncoder is reading this for his documentation.

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

Re: Supplying Arguments to viper function

Post by pythoncoder » Sun Feb 14, 2016 9:01 am

I am indeed reading this ;)

The following verifies that the pointer is working:

Code: Select all

@micropython.viper       
def data_ptr(data: ptr8):
      data[0] = 1
      data[1] = 2
a = bytearray(b"abc")
data_ptr(a)
print(a)
The outcome is

Code: Select all

bytearray(b'\x01\x02c')
One way to get the length is to pass the bytearray and convert to a pointer within the code:

Code: Select all

from uctypes import addressof
@micropython.viper       
def foo(data):
    print(data, len(data))
    p =ptr8(addressof(data))
    p[0] = 97
    print(data, len(data))
Producing

Code: Select all

>>> a = bytearray('the quick brown fox')
>>> foo(a)
bytearray(b'the quick brown fox') 19
bytearray(b'ahe quick brown fox') 19
>>> 
Peter Hinch

User avatar
Roberthh
Posts: 2714
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Supplying Arguments to viper function

Post by Roberthh » Sun Feb 14, 2016 10:30 am

Hello Pythoncoder, thanks for the hints. I tried to integrate that fpr my code, but something is strange. I have a piece of code that works:

Code: Select all

import pyb, stm
from uctypes import addressof   
    @micropython.viper        
    def tft_cmd_data(self, cmd: int, data: ptr8, size: int):
        gpioa = ptr8(stm.GPIOA + stm.GPIO_ODR)
        gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL)
        gpioa[0] = cmd          # set data on port A
        gpiob[1] = D_C | WR     # set C/D and WR low
        gpiob[0] = D_C | WR     # set C/D and WR high
        for i in range(size):
            gpioa[0] = data[i]  # set data on port A
            gpiob[1] = WR       # set WR low. C/D still high
            gpiob[0] = WR       # set WR high again 
That piece works, I see four write pulses, and the right data. In an attempt to omit the third argument of the function, I tried this

Code: Select all

import pyb, stm
from uctypes import addressof
     @micropython.viper        
    def tft_cmd_data2(self, cmd: int, data):
        gpioa = ptr8(stm.GPIOA + stm.GPIO_ODR)
        gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL)
        gpioa[0] = cmd          # set data on port A
        gpiob[1] = D_C | WR     # set C/D and WR low
        gpiob[0] = D_C | WR     # set C/D and WR high
        data_ptr = ptr8(addressof(data))
        ld = int(len(data))
        print(len(data), ld)
        for i in range(ld):
            gpioa[0] = data_ptr[i]  # set data on port A
            gpiob[1] = WR       # set WR low. C/D still high
            gpiob[0] = WR       # set WR high again 
The size of data is three, and that is what was printed, but the loop seems not to be executed. I see a single write pulse only.
Do you have any clue what's happening? Not that this is important, since the first variant works. But calls to the second variant would be more robust (bytes constant, not length to be supplied = one error-option less).

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

Re: Supplying Arguments to viper function

Post by pythoncoder » Sun Feb 14, 2016 11:05 am

As far as I can see it should work: you may have found a bug in Viper. I can't run the code as presented without the class definition, but the following stripped down function does work.

Code: Select all

from uctypes import addressof
@micropython.viper       
def tft_cmd_data2(self, cmd: int, data):
    data_ptr = ptr8(addressof(data))
    ld = int(len(data))
    print(len(data), ld)
    for i in range(ld):
        print(i, data_ptr[i])
Outcome

Code: Select all

>>> a = bytearray([1,2,3])
>>> tft_cmd_data2(1, 2, a)
3 3
0 1
1 2
2 3
>>> 
The best way to proceed is progressively to strip down the code to the minimum which exhibits the failure and raise an issue on GitHub.
Peter Hinch

User avatar
Roberthh
Posts: 2714
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Supplying Arguments to viper function

Post by Roberthh » Sun Feb 14, 2016 11:37 am

I found it. It's no bug, just a feature: changing the code makes is much slower. The first variant had about 500 ns between the first and second write, and 450 ns for the following ones. The second variant had like 14 µs between the first and second pulse, and 540 ns within the loop. addressof() seems to take 4 µs, len(data) the other 8.5. So: I learned something. Whether this method is appropriate, depends.

BT.W. I have a series of these calls in my code. The time between these function call is like 20 µs. Since this is still the init section, time does not really matter, especially since there are various intentional delays in between, e.g. to allow the PLL to settle. When sending pixel data, speed matters.

Damien
Site Admin
Posts: 642
Joined: Mon Dec 09, 2013 5:02 pm

Re: Supplying Arguments to viper function

Post by Damien » Sun Feb 14, 2016 12:42 pm

Guys, you don't need to use the addressof operator to get a pointer to the bytearray. The "ptrX" cast will do it for you. Here are the rules:
  • Casting operators are currently: int, uint, ptr, ptr8, ptr16, ptr32
  • Result of a cast will be a native viper variable
  • Arguments to a cast can be a Python object or a native viper variable
  • If argument is a native viper variable, then cast is a no-op (ie costs nothing at runtime) that just changes the type (eg from uint to ptr8 so that you can then store/load using this pointer
  • If argument is a Python object and the cast is int or uint, then the Python object must be of integral type and the value of that integral object is returned
  • If argument is a Python object and the cast is ptr/ptr8/ptr16/ptr/32, then the Python object must either have the buffer protocol with read-write capabilities (in which case a pointer to the start of the buffer is returned) or it must be of integral type (in which cas the value of that integral object is returned)
Also, @Robberthh: as you found out, calling ptr8 on an object will take a few microseconds (and so will the print). So you should do all this stuff at the start of the viper function, before any of the timing-critical code.

User avatar
Roberthh
Posts: 2714
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Supplying Arguments to viper function

Post by Roberthh » Sun Feb 14, 2016 1:21 pm

Hi Damien, thanks for the explanation. The print statements were only in for debugging. The ptr8 cast adds 1,3 µs, which is acceptable, especially when there is a lot of data to be written. In contrast, the addressof() call takes about 4µs, but I may supply both bytes and bytearray objects, which save the time for creating a once used bytearray object. I do not know whether this (addressof() on bytes object) is intentional, because it may allow to modify an immutable object. The len(x) is slower, but that has to be determined at some place anyhow. At the end, the total time is similar- kind of another law of conservation.

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

Re: Supplying Arguments to viper function

Post by pythoncoder » Sun Feb 14, 2016 6:25 pm

Damien wrote:Guys, you don't need to use the addressof operator to get a pointer to the bytearray. The "ptrX" cast will do it for you. Here are the rules...
Thanks for that, I'll incorporate the info into my doc, hopefully tomorrow.
Peter Hinch

Post Reply