Minimum Pokitto Library

Does your project need every last byte of flash?
Do you need just the bare minimum to get your code running?
Do you want to write in a language other than C++ and need to get the hardware setup so you can do your thing?

Then here you go:

While PokittoLib/FemtoLib/MicroPython try to give you everything you could need to make a game, the minimum library gives you hardware initialization and 8 11 functions.
Hereā€™s an example featuring the entire API:

#include "PokittoMini.h"
// size with -O3: 2448 bytes
// size with -Os: 2084 bytes

int main(){
    enableDAC(); // enable sound
    int i=0;
    int x=0, t=0;
    while(true){
        if(aBtn() || bBtn() || cBtn() || upBtn() || downBtn() || leftBtn() || rightBtn())
            writePixel(i++); // write a color to the screen
        if( !x-- ){
            writeDAC((t>>5)|(t>>4)|t++); // write some sound
            x = 500;
        }
    }
}

Thatā€™s it. If you need anything else, either re-implement it or rip it out of PokittoLib/FemtoLib.

To invoke the loader, hold C while turning the Pokitto on.

9 Likes

Firstly, this doesnā€™t have a licence.
(If it was all pulled from the PokittoLib then it should still be BSD 3-clause.)

Secondly, what language and/or environment is this supposed to be for?
The .cpp file looks like a mishmash of C and C++ code,
and the header wouldnā€™t compile as C anyway.
(Because of the reinterpret_cast, not because of the bool. It could be made to compile as C with a few tweaks, e.g. including <stdbool.h>.)

Lastly youā€™ve got u8 in the header, which isnā€™t actually defined anywhere.
Youā€™re also using uint32_t (without std::) but arenā€™t including any header to actually define it first.)

(Also now I think about it you seem to be defining cBtn twice as well. Once in the header, once in the .cpp.)

Does this mean, I could configure a sort of transpiler for Groovy, how you made Javitto?

I donā€™t know what the original licence for startup.cpp is. Itā€™s based on mbedā€™s startup_LPC11U68.cpp. The intent is to phase that cpp file out completely, moving all of that code out to SystemInit.s.

SystemInit.s is going to be PD/CC0. Iā€™ll have to add a LICENCE file saying so.

Howā€™s this for a more C-compatible header?

inline bool aBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 9)); }
inline bool bBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 4)); }
inline bool cBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 10)); }
inline bool upBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 13)); }
inline bool downBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 3)); }
inline bool leftBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 25)); }
inline bool rightBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 7)); }
inline void writePixel(int color){
    volatile int *LCD = (volatile int*) (0xA0002188);
    LCD[124>>2] = 1<<12;
    LCD[ 0 ] = color<<3;
    LCD[252>>2] = 1<<12;
}

Defining cBtn twice isnā€™t a problem since its marked inline.
Jonne has pointed out that it would also be good to have a function for writing sound, so Iā€™ll add that too.


From what I understand, Groovy isnā€™t going to transpile very well to C++. Maybe porting one of the microcontroller-based JVMs would be a better idea?

1 Like

I demand that! Seems like compiling the project right now is not out of the box.

1 Like

Updated the first post with a working FemtoIDE project.
Just drop that into your projects folder and build.
Also added:

  • enableSysTick() (but you have to supply your own systick function for time-tracking)
  • enableDAC() and writeDAC(unsigned char) for sound.

We are now at 11 functions.

3 Likes

If itā€™s the one from mbed-os then it should be Apache 2.0.
(I looked and couldnā€™t find an overriding readme for LPC11U6X, though some other NXP chips have one.)

I have mixed feelings about that.
On the one hand it has the potential to produce smaller/faster code,
but on the other hand itā€™s easier to mess up assembly and harder to maintain it,
so itā€™ll need liberal commenting.

Youā€™re still missing the includes, but otherwise itā€™s closer to C compatibility.

(Assuming C compatibility is an intention of course. Personally Iā€™d rather have pure C++11 than a C/C++ mish-mash.)

Itā€™s not a compilation problem, but itā€™s still a bit awkard (i.e. not very ā€˜DRYā€™).


I think the main problem with Groovy is that itā€™s dynamically typed and supports duck typing,
neither of which transpile well to C++.
Thatā€™s the kind of thing that only really works well on a VM or with lots of memory (to maintain hash tables).

1 Like

Love the concept on light lib.
just add the minimum and go :slight_smile:

1 Like

This library isnā€™t specifically meant for C/C++. ā€œLibraryā€ might be a bit of a misnomer: once the startup.cpp code is translated to assembly, this would probably be closer to a bootloader, as its primary concern is to leave the hardware in a ready-to-run state. The header file, supplied for convenience, is not actually needed at all and can be removed (which is why startup.cpp has its own cBtn). An example of this is if someone were to try to make a game entirely in assembly, in which case this would be a great starting point and the header wouldnā€™t be needed.

I really started to like asm/4k gamejam idea over this lib!
But I still think the next jam should be done with any programming language one chooses. But maybe after that we can have the ASM Jam.

4 Likes

Then perhaps the other buttons should also (eventually) be implemented in assembly and the header should just act as a function declarer/exporter?

1 Like

Iā€™ll be waiting until licensing is clear but am loving this, thank you very much :slight_smile: Could be very useful for my C projects. I also like the API, writing sound seems like Unix. Great job.

3 Likes

Reading button state is litterally two ops:

LDR r0, =A_BTN
LDR r0, [r0]

Having a function for this is a bit odd. The proper way to do this in asm would probably be to use a macro instead of an actual function.
In C/C++, these functions would normally be inlined. Having them call assembly would prevent that, so Iā€™d prefer to violate the DRY principle.


About the licensing, the updated lib specifies that SystemInit.s is in the public domain. The rest is going to be rewritten so it can be PD too, but it will take a while since it isnā€™t a top priority. Once it is rewritten, however, it will be compatible with the existing code so updating will be safe and painless.

3 Likes

Iā€™m having trouble compiling this, could anyone help?

Iā€™m using the commands from Makefile:

~/Git/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gcc -x assembler-with-cpp -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-framale-pointer -g0 -DMBED_RTOS_SINGLE_THREAD -mcpu=cortex-m0plus -mthumb -D__CMSIS_RTOS -D__MBED_CMSIS_RTOS_CM -D__CORTEX_M0PLUS -DARM_MATH_CM0PLUS -o SystemInit.o SystemInit.s
~/Git/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-g++ $EXTRA_FLAGS -c -std=gnu++98 -fno-rtti -Wvla -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -DMBED_RTOS_SINGLE_THREAD -mcpu=cortex-m0plus -mthumb -DTARGET_LPC11U68 -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DTARGET_NXP -D__MBED_CMSIS_RTOS_CM -DDEVICE_RTC=1 -DTOOLCHAIN_object -D__CMSIS_RTOS -DTOOLCHAIN_GCC -DTARGET_CORTEX_M -DTARGET_M0P -DTARGET_UVISOR_UNSUPPORTED -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DTARGET_LPCTarget -DTARGET_CORTEX -DDEVICE_I2C=1 -D__CORTEX_M0PLUS -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DMBED_BUILD_TIMESTAMP=1526394586.66 -DARM_MATH_CM0PLUS -DTARGET_LPC11U6X -DDEVICE_SLEEP=1 -DTOOLCHAIN_GCC_ARM -DDEVICE_SPI=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_LIKE_CORTEX_M0 -o main.o main.cpp
~/Git/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gcc -T project.link_script.ld -Wl,--gc-sections -Wl,--wrap,main -Wl,--wrap,_memalign_r -Wl,-n --specs=nano.specs -mcpu=cortex-m0plus -mthumb -o main.elf SystemInit.o main.o -Wl,--start-group -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys -Wl,--end-group
~/Git/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-objcopy -O binary main.elf main.bin

It compiles, but linker gives a warning:

arm-none-eabi/bin/ld: warning: cannot find entry symbol ResetISR; defaulting to 0000000000000000

and running fails with:

Attempt to write to flash (0x0) on PC=0x4001c012

EDIT:

Oh I forgot to compile startup.cpp. But now I get:

startup.cpp:9:15: error: expected nested-name-specifier before 'Reg'

EDIT:

Okay, seems to work now with #define Reg(x) ((volatile unsigned int *)(x)).

Since this is only a few fairly short source files, I am thinking about rewriting everything, perhaps putting everything into one file, and licensing it CC0.

Having no idea what Iā€™m doing on this low level Iā€™ve trial-and-error reduced everything into a single < 150 LOC .h file:

inline unsigned char aBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 9)); }
inline unsigned char bBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 4)); }
inline unsigned char cBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 10)); }
inline unsigned char upBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 13)); }
inline unsigned char downBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 3)); }
inline unsigned char leftBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 25)); }
inline unsigned char rightBtn(){ return *((volatile char*)(0xA0000000 + 1*0x20 + 7)); }

inline void writePixel(int color){
    volatile int *LCD = (volatile int*) (0xA0002188);
    LCD[124>>2] = 1<<12;
    LCD[ 0 ] = color<<3;
    LCD[252>>2] = 1<<12;
}

inline void enableSysTick(){
    volatile unsigned int *SysTick = (unsigned int *) 0xE000E010UL;
    // SysTick[1] = 4800000-1; // OSCT = 0
    SysTick[1] = 7200000-1; // OSCT = 2
    SysTick[2] = 0;
    SysTick[0] = 4 | 2 | 1; //CLKSOURCE=CPU clock | TICKINT | ENABLE
}

inline void enableDAC(){
    volatile unsigned int *PIO1 = (volatile unsigned int *) 0x40044060;
    volatile unsigned int *PIO2 = (volatile unsigned int *) 0x400440F0;
    volatile unsigned int *DIR1 = (volatile unsigned int *) 0xA0002004;
    volatile unsigned int *DIR2 = (volatile unsigned int *) 0xA0002008;
    PIO1[28] = PIO1[29] = PIO1[30] = PIO1[31] = 1<<7;
    PIO2[20] = PIO2[21] = PIO2[22] = PIO2[23] = 1<<7;
    *DIR1 |= (1<<28) | (1<<29) | (1<<30) | (1<<31);
    *DIR2 |= (1<<20) | (1<<21) | (1<<22) | (1<<23);
}

inline void writeDAC(unsigned char out){
    volatile unsigned char *P1 = (volatile unsigned char *) 0xA0000020;
    volatile unsigned char *P2 = (volatile unsigned char *) 0xA0000040;
    P1[28] = out&1; out >>= 1;
    P1[29] = out&1; out >>= 1;
    P1[30] = out&1; out >>= 1;
    P1[31] = out&1; out >>= 1;
    P2[20] = out&1; out >>= 1;
    P2[21] = out&1; out >>= 1;
    P2[22] = out&1; out >>= 1;
    P2[23] = out;
}

#define LPC_SYSPLLCTRL 0x40048008  

#define LPC_GPIO_PORT_MPIN2 0xA0002188

#define LPC_GPIO_PORT_CLR0 0xA0002280
#define LPC_GPIO_PORT_CLR1 0xA0002284
#define LPC_GPIO_PORT_CLR2 0xA0002288

#define LPC_GPIO_PORT_SET0 0xA0002200
#define LPC_GPIO_PORT_SET1 0xA0002204
#define LPC_GPIO_PORT_SET2 0xA0002208

#define LCD_CD_PIN 2
#define LCD_WR_PIN 12

#define LCD_CD_SET LPC_GPIO_PORT_SET0
#define LCD_CD_CLR LPC_GPIO_PORT_CLR0
#define LCD_WR_SET LPC_GPIO_PORT_SET1
#define LCD_WR_CLR LPC_GPIO_PORT_CLR1
#define LCD_MPIN LPC_GPIO_PORT_MPIN2
     
extern "C"
{
    extern void _vStackTop(void);
    
    void ResetISR(void);
    void voidHandler();

    __attribute__ ((section(".isr_vector")))
void (* const vectors[])(void) = 
{
    &_vStackTop,
    ResetISR,
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,              
    voidHandler,             
    voidHandler,            
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler,
    voidHandler
};

extern int main(void);
  
#define SET_WORD(addr,val) *((unsigned int *) addr) = val

__attribute__ ((section(".after_vectors"))) void ResetISR() 
{
  SET_WORD(LPC_SYSPLLCTRL,0x25);
  SET_WORD(LCD_CD_CLR,1 << LCD_CD_PIN);
  SET_WORD(LCD_MPIN,0x22 << 3);
  SET_WORD(LCD_WR_CLR,1 << LCD_WR_PIN);
  SET_WORD(LCD_WR_SET,1 << LCD_WR_PIN);
  SET_WORD(LCD_CD_SET,1 << LCD_CD_PIN);
  main();
}

__attribute__ ((section(".after_vectors"))) void voidHandler() {};

}

Is it possible that it can work without all that stuff Iā€™ve removed? It compiles and works in emulator (buttons and sound too), the display is written vertically, and of course you canā€™t enter the loader, though I canā€™t try it on real HW, which might not work.

  • The emulator doesnā€™t need most of the initialization code that hardware does. If you want to alter initialization, itā€™s important to test on hardware and look at the specs to be sure of the consequences. As it is, I have no idea what clockspeed the CPU will run at.
  • It is important to keep the code that calls the bootloader, since that is something that every Pokitto game should do. While not necessary from a technical perspective, it is important to players.
  • The way youā€™ve setup the vectors table you canā€™t define an interrupt handler without editing the file. Itā€™s good to keep the alias/weak stuff.
  • Without interrupt handlers, SysTick (and any other form of high-resolution time-keeping) is broken.
  • SET_WORD should cast to volatile unsigned int*, especially if itā€™s meant to be used outside the header. If it isnā€™t, then itā€™s good to undefine it when itā€™s no longer needed.
  • You removed the code that initializes static memory. I imagine RAM will contain noise. I donā€™t know if thatā€™s OK in C, but it will break C++.
  • IMHO, in this case, putting everything inside a single header file will cause more problems than it solves. You still need the linker script so might as well have a library file to link against.
2 Likes

If by ā€˜static memoryā€™ you mean the area of memory where things with static storage duration (e.g. global variables and function-local static variables) then C mandates that:

If an object that has static storage duration is not initialized explicitly, it is initialized implicitly as if every member that has arithmetic type were assigned 0 and every member that has pointer type were assigned a null pointer constant.

So not initialising that block of memory is at least contrary to Cā€™s spec and could have notable consequences.

I concur.

The .h-.c/.cpp split doesnā€™t exist solely for the purpose of making larger programs easier to compile, there are some technical reasons within the rules of both C and C++ why having multiple translation units is useful and can actually simplify things compared to only using .h files.

Iā€™d really love to have a single .h file and only compile once, that would be really neat, simple and fast, and even more cool if the entire Makefile could be replaced with a single one liner command runnable from command line on any platform, really earning the title minimal. Why is the linker script even needed? Why canā€™t it work like with normal x86 where I need no scripts? Anyway, Iā€™ve been able to compile it this way, so there seem to be no problems, it just seems pretty lame that a single file canā€™t be compiled into elf with a single command.

1 Like

Three files doesnā€™t seem that bad in the grand scheme of things. Itā€™s not that much slower.
Compared to compiling the whole PokittoLib itā€™s pretty minimal.

You could complain to and/or ask the GCC maintainers about it.

As far I see it, the main problem is thereā€™s a lot of intermediate phases involved.
Preprocessing, compiling, assembling, linking, converting to .bin.
And each of those phases can be customised, hence each is a separate command,
and in some cases is actually a separate program (in keeping with the Unix philosophy).
(At least one of those phases (preprocessing) is an historical artefact that most modern languages donā€™t have to deal with.)

It would be nice if GCC could be directed to just build the .bin and skip building the .elf, but again that seems to be a case of ā€œdifferent programs for different jobsā€ (i.e. objcopy is probably the only one in the chain that actually knows what a .bin is), and of course .bins are in less demand than .elfs.

Most likely the answer to this is that x86 dominates the tech landscape, so it gets special treatment.
ARM chips arenā€™t often used for desktops or laptops,
theyā€™re used for embedded systems, games consoles and tablets,
so thereā€™s less demand for simpler/user-friendly tools.
Also even low-end x86 chips are much more powerful and typically have access to an order of magnitude more RAM than most ARM chips.

I must admit Iā€™d be interested in an answer to this question too.
Iā€™ve always assumed itā€™s simply a means of making sure the data goes to the correct addresses for a Cortex-M0, as opposed to some other CPU model.
(And of course calculating the CRC that certain ARM chips apparently need to verify the code.)

Iā€™m reading about it now and they say a linker script is always used, but if you donā€™t supply one a default one will be used, which is what works with x86 on a specific operating system with highly standardized executable format. But with so many ARM platforms and bare metal you donā€™t have such standardized memory layouts, which is why you need to supply your own script. Thatā€™s my guess.

1 Like