Microsoft MakeCode

Only 4 parameters allowed in C functions? Really?

It has taken me rather a long time tonight to realise that the only thing stopping a C function, that I’ve put in a .cpp file as part of an extension I’m writing, from compiling correctly is that it has five parameters.

void pLine(int x0, int y0, int x1, int y1, bool state) {
}

Seriously? I can’t have more than four parameters to a function even though I can have five in Javascript?

Yes, it’s a compiler limitation. You can usually get around it by “packing” x/y arguments into a single int instead of two separate ones. Then you can unpack them on the cpp side.

A good example for this is the screen package in pxt-common-packages.

We pack the arguments in the TS like this

And unpack in the cpp like this

Thank you. Yes, I just realised that I’d read that function parameters are passed directly via the ARM registers. Whilst I think this means in theory there could be more than four parameters allowing that to happen puts heavy restrictions on coding practice.

I can pack my co-ords. But I just thought it good to create a separate function setState(bool state) which sets a static variable so that the line/rectangle/box plotting functions can then be called without the fifth parameter. This bool flag is for a draw/undraw state and applies to to all plotting functions.

By the way, is my state variable best defined as a static? I’m not up on C at all, so I’m not sure. I’ve also got two look-up tables for bitwise manipulations because this was a quicker method in Javascript than doing it from scratch.

Can you please tell me if I’ve used the most appropriate way to define my constants and variables? I have also created the buffer - 504 bytes for the LCD display - in C so that I can pass it to Javascript through initBuffer().

namespace nokialcd {
    static const uint8_t FILL_X[9] =  {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00};
    static const uint8_t FILL_B[9]  = {0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff, 0xff};
    static Buffer bytearray = NULL;
    static bool state = true;
    //%
    Buffer initBuffer() {
        bytearray = mkBuffer(NULL,504);
        return bytearray;
    }
    //%
    void setState(bool s) {
        state = s;
    }
  ...and then the main pixel/line/screen-scroll functions...
}

Also I used to have a table of [0,84,168…] to multiply by 84 but I discovered, with run-time tests, that in C it’s actually quicker to do int r = (y >>3) * 84 than it is to do int r = YMUL[y >>3]. That’s interesting.

Oh, I am not at all qualified to critique C code! I mostly stick to the TypeScript side of things :smile:

Best I can do is point you to common-packages. I believe we usually put state on a singleton instance of a class, here’s where we do that in our accelerometer code:

Hopefully one of the hardware folks can chime in with some better advice.

Thanks. I’ll try and process that tomorrow. Also, having done some runtime tests I can see clearly that C is often three or more times faster, and this is true of my scroll routine which shifts parts of the screen buffer or the whole thing, using for loops.

My question is, if a for loop performing this is compiled as sensible ARM code from the C but isn’t at all efficient from the Javascript, how much time is being wasted when I deliver the 504 byte buffer to the screen via the spiWrite() function? It’s very time-consuming?

Can you please tell me if there is a way I can do this in C or directly in assembler? I’ve taken note of the assember function in the WS2812b package that Peli (?) wrote for the Neopixel drivers.

I would really like to know if it’s possible if it is the case that Javascript delivers bytes to the SPI device at a slower rate than it can pump them out to external devices. Are you able to ask around?

The limiting factor for driving displays from a serial bus is the data transfer.

Michael

Here’s a pic of my Microbit Nokia-LCD gadget:

I agree it’s a bit annoying to have to pack arguments to work-around the 4 register limitation. Passing more would require some work on the compiler though, as they need to go on the stack.

As a rule of thumb, you should expect the low-level code to be 10x slower in TypeScript than in C (as a comparison you should expect 100x in MicroPython or similar interpreters). If you want, you can read up on the performance results here https://www.microsoft.com/en-us/research/publication/static-typescript/

I wrote the neopixel in assembly not for performance, but because it requires very precise timings. You should not need to do that for an SPI screen. In fact, in MakeCode Arcade most of the screen operations are done in TypeScript and are fast enough. Granted, most Arcade hardware is about 6x faster than micro:bit but we’re also talking about much larger, color screen.

I would suggest sticking to TypeScript, then measuring and seeing where are the bottlenecks.

Good luck with your project, it looks really cool!

Thanks. I have already converted most of my routines to C - not too painful, thankfully. The four parameter limit is thankfully not a show-stopper.

Whilst there may be plenty of time to do any processing that’s necessary, with a 16MHz ARM, it remains the case that with a 1MHz SPI clock speed the theoretical minimum screen transfer time for 504 bytes should be just over 4ms excluding set-up times - that’s 504 x 8 clock cycles.

However with a 16MHz ARM processor running from the Javascript code my tests show it takes about 13.9ms. In other words, after each set of 8 SPI clock cycles delivering one byte of data there are an additonal 20 potential SPI clock cycles of inactivity. That’s means approx 320 processor clock cycles at 16MHz to handle the function call to spiWrite() and the trivial calculations for the FOR loop.

        pins.digitalWritePin(LCD_CE, 0)
        for (let i = 0; i < 504; i++) {
            pins.spiWrite(bytearray[i])
        }
        pins.digitalWritePin(LCD_CE, 1)

So can I please request some sort of acces to the spiWrite() function in C for any future updates? Or even in ARM assembler? Is there anything possible in this respect at the moment?

Perhaps I’ll put this into a Github request.

Michael
.