MitchBradley/cforth

Callbacks in C

jonsmirl opened this issue · 7 comments

I have a callback like this

static void btn_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target(e);
    printf("Button %p code %d\n", btn, code);
}

I made two words.

cell ((* const ccalls[])()) = {
	C(lv_obj_add_event_cb)	//c lv-obj-add-event-cb { i.user_data i.filter i.event_cb i.object -- i.desc}
	C(btn_event_cb)		//c btn-event-cb  { i.event -- }

So how do I get the address of my C routine as that i.event_cb parameter? The callback will go straight to C, then if the C code needs to, it will call forth.

Make a ccall function that returns the address of the C routine.

I implemented the ccall returning the address, now I need locks in the system. The two tasks are disrupting each other in the LVGL code. It is not reasonable to lock the LVGL code since it has hundreds of APIs. Much easier to lock the forth side of things. I have to stop that LVGL timer tick from running while forth is active.

So I need to lock this:

    spush(cmd, callback_up);
    spush(strlen(cmd), callback_up);
    execute_word("evaluate", callback_up);

and then this

    execute_word("app", callback_up);

On this one I will have to release and reacquire the lock continuously in that keyboard poll. Maybe I can rework that poll to sleep until interrupted and not beat on the locks. Does that ESP linenoise lib support anything like that? I've never used it.

Finally I have to lock this, because this is what is interfering with the forth task.

        lv_timer_handler();

I also considered making a forth word to lock/unlock. That seems risky because if the forth code errors out and stops executing, it will leave the lock in place.

I think the solution is to use a large timeout here, which will eliminate banging on the locks.

int raw_poll(unsigned char *c)
{
    return uart_read_bytes(0, c, 1, 0);
}

Edit: I dug down deeper into uart_read_bytes(). prvReceiveGeneric() looks like it is in a hard loop waiting for an ISR to supply a character but it is yielding by continuously taking and releasing a semaphore. Don't know why they don't sleep and use the ISR to wake the task. This prevents the system from going into deep_sleep via the idle task.

You can guard against the Forth word erroring out with catch. It won't help if the Forth word blocks in an infinite loop.

If the prvReceiveGeneric() thing makes the longer timeout ineffective, then you could just change the argument to vTaskDelay() so uart_read_bytes() is called less frequently. The interactive latency will suffer but that is probably okay if you are only using the Forth interpreter for infrequent debugging. I suppose you could also make it adaptive so the task delay is long when characters haven't been typed in awhile, and shortens when a character is seen. Then you have a long initial latency, but fast response once the interpreter "wakes up".

Could you run the LVGL and Forth tasks on different cores? I would consider running Forth on the core that runs the WiFi stack, and LVGL on the other one.

I have a working set of locks in place now. Keyboard poll is keeping from entering sleep mode but I can avoid the issue by putting forth UART support onto a switch which only turn it on for debugging.