I’m currently not running into any problems, because I just write the solution in C, but I’d like to have the user be able to utilize the lisp REPL to write non blocking code that can respond to events without having to rely on a busy loop like what you are doing.
If I’m not mistaken, this busy loop blocks the REPL from returning. If you go down that route you have to constantly poll the status of millis or volatile variables, and garbage collection is never handled by the system, so you have to call (gc) periodically.
It would be much nicer if one could spawn separate async tasks that park their operation and wait for incoming events. The events could be appended to a uLisp stream buffer by a interrupt handler so that interrupts can be dealt with succinctly and events aren’t missed when the REPL is dealing with other calculations.
Clojure deals with this sort of multi-tasking with go-blocks and channels. A channel is a message queue on which one could stream incoming and outgoing events messages. The go blocks are bits of code that get compiled down into an independent state machine that can deal with incoming messages, and otherwise are parked.
In essence, it would be like your above loop structure, that are independent, and only get called when an event triggers them (interrupts).
So far I’ve just been using the FreeRTOS tasks and timers to do async functionality on the two cores of Esp32s:
TimerHandle_t theTimer = NULL;
void TimerCallback(TimerHandle_t xTimer) {
... whatever you want to run when the timer calls...
}
void setup() {
theTimer = xTimerCreate(
"theTimer", // Timer name
1000 / portTICK_PERIOD_MS, // 1s period
pdTRUE, // Auto-reload (periodic timer)
NULL, // Timer ID
TimerCallback // Callback function
);
if (theTimer == NULL) {
Serial.println("Failed to create timer!");
while (1);
}
xTimerStart(theTimer, 0); // Start timer immediately
}
or async tasks pinned to alternate cores that talk over a queue:
TaskHandle_t sendTask;
TaskHandle_t recieveTask;
struct packet {
unsigned char auth[AUTH_LEN];
unsigned char message[MESSAGE_LEN];
};
QueueHandle_t queue;
void setup() {
//create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
xTaskCreatePinnedToCore(
sendTask, /* Task function. */
"SendTask", /* name of task. */
10000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
&sendTask, /* Task handle to keep track of created task */
0); /* pin task to core 0 */
xTaskCreatePinnedToCore(
recieveTask, /* Task function. */
"RecieveTask", /* name of task. */
10000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
&recieveTask, /* Task handle to keep track of created task */
1); /* pin task to core 1 */
queue = xQueueCreate( 10, sizeof( struct packet ) );
if(queue == NULL){
Serial.println("Error creating the queue");
}
}
void sendTask( void * pvParameters ){
for(;;){
struct packet myPacket;
myPacket.auth = "Bob"
myPacket.message = "Hello World!"
xQueueSend(queue, &myPacket, portMAX_DELAY);
}
}
void recieveTask(void *parameter) {
for (;;) {
struct packet recievePacket;
if (xQueueReceive(queue, &recievePacket, portMAX_DELAY)) {
Serial.printf("Recieved %s from %s\n", recievePacket.message);
}
}
}
void loop() {
if(queue == NULL){
Serial.println("Queue was not created");
return;
}
...
}