alvieboy

ZrT (ZPUino RunTime)

Recommended Posts

Hi guys,

 

since the last few days I have been working on ZrT - ZPUino RunTime (or Real-Time, as you wish).

 

What does this mean ? This means you now can have threads/tasks in the ZPUino environment, and use common primitives to synchronize them and exchange data.

 

The main driver for this was actually the Wii Nunchuch driver. It's I2C, but requires large delays (some are 10ms), and delays are blocking. This was causing my video demo to lose bytes from the serial port, because I was streaming video at a high rate. So, in order to solve this problem, I would had to run code while the Chuck code was waiting (or even waiting for the I2C channel to finish).

 

So I picked up some previous work (including some techniques I had used for Linux on ZCoreV3) and decided to implement a small and fast multitasking system that could be used almost transparently (meaning small no impact  on already-developed code).

 

it uses a simple priority-based scheduler, and implements a run-queue which depicts all tasks ready to run. The list head always points to the next task to run. Whenever a task is picked, it is moved down the list of tasks with same priority, thus implementing a round-robin schema for tasks with same priority.

 

Whenever a task needs to wait for some resource, or a for time-delay, it is simply removed from the run-queue. It can be placed in either a wait-queue (used for mutexes, for example), or a timer-queue (used for timers, ordered by expiration time).

 

There are still some small things to tune, but it looks like it works OK.

 

Here's an example usage code, with three tasks/threads (fourth task, which is "loop()", it's suspended)

#include "Arduino.h"#include "zrt_task.h"#include "zrt_timer.h"zrt_mutex printMutex;void myTask1(){    while (1) {        zrt_mutex_lock(&printMutex);        Serial.println("This is task 1");        zrt_mutex_unlock(&printMutex);    }}void myTask2(){    while (1) {        zrt_mutex_lock(&printMutex);        Serial.println("This is task 2");        zrt_mutex_unlock(&printMutex);    }}void myTask3(){    while (1) {        zrt_mutex_lock(&printMutex);        Serial.println("This is task 3");        zrt_mutex_unlock(&printMutex);        zrt_sleep(15);    }}void setup() {    Serial.begin(115200);    zrt_mutex_init(&printMutex);    zrt_createTask( &myTask1, ZRT_DEFAULT_PRIORITY, ZRT_DEFAULT_STACK );    zrt_createTask( &myTask2, ZRT_DEFAULT_PRIORITY, ZRT_DEFAULT_STACK );    zrt_createTask( &myTask3, ZRT_DEFAULT_PRIORITY, ZRT_DEFAULT_STACK );}void loop() {    // put your main code here, to run repeatedly:    // Suspend ourselves    zrt_suspend( zrt_currentTask );    zrt_schedule();}

There are still some things I don't like, notably how mutex unlocking is re-scheduling the next task.

$ ./tb --ieee-asserts=disable This is task 3This is task 2This is task 2This is task 1This is task 2This is task 2This is task 1This is task 1This is task 2This is task 1This is task 1This is task 2This is task 2This is task 1This is task 2This is task 2This is task 1This is task 1This is task 2This is task 2This is task 1This is task 2This is task 2This is task 1This is task 1This is task 2This is task 1This is task 1This is task 3This is task 2This is task 2This is task 2This is task 1This is task 2

I don't think that in this scenario (where two threads are trying to acquire the same resource to exhaustion) there should be a requirement that they are scheduled in a round-robin fashion, but I think that can also be done.

 

Comments ?

Share this post


Link to post
Share on other sites

I forgot to mention the "oddest" of the functionalities, which I am evaluating: Wait for interrupt/Wait for bit.

 

This is mainly to avoid loops when waiting for some flags on IO devices to be set. Having to implement a full driver (interrupt-based) for those devices is often too difficult for users, even for those developing libraries.

 

Some IO devices can do this by triggering an interrupt on some of their events.

 

But IO devices which don't actually implement interrupts can also be made working - we can add support on the sysctrl to "poll" in hardware for specific bits in specific IO addresses, and raise the interrupt whenever they are seen. Again, this is pretty much "odd", in the sense I have never seen this in any system  - and I know quite a few.

 

Alvie

Share this post


Link to post
Share on other sites

Sweeet! This will make things easier for sure, we've run into situations where we needed to be careful not to disrupt timing critical things, such as feeding data to the audio chips. This will make those problems much easier to deal with.

 

Thanks!

Jack.

Share this post


Link to post
Share on other sites

If you are doing an rt system then you can hide interrupts from users by making an interrupt an event. You never see an "interrupt" just your thread gets woken up again. Making that work often needs support for priority inversion handling and priorities but I suspect you need them anyway and deadlock detection if you want it to be reasonably userproof.

 

Controllers monitoring bits of I/O space seems sensible - it's not new, floppy controllers generally polled the disk change lines of the disks and turned it into an IRQ. Some ethernet controllers support similar PHY polling schemes so the hardware polls the phy regularly and checks for certain changes.

Share this post


Link to post
Share on other sites

 

it's not new, floppy controllers generally polled the disk change lines of the disks and turned it into an IRQ. Some ethernet controllers support similar PHY polling schemes so the hardware polls the phy regularly and checks for certain changes.

 

Yes, but I was referring myself as something more high-level. Controllers surely can do this, and many do that exactly, but I am not aware of a agnostic controller (think DMA-like approach) that polls for hardware/mmio registers and triggers interrupts based on values read.

 

Alvie

Share this post


Link to post
Share on other sites

I can't think of a generic case but then you would wasting main memory bus bandwidth polling the address rather than being event triggered. The Z80 DMAC can do it (interrupt on match) but it was almost never a win to do so because of the bus contention.

Share this post


Link to post
Share on other sites

We can do it only at IO bus (and that was my idea), so that main memory bus is not disturbed.  So that would be a feature of the IO controller itself.

 

Also, since IO transactions are forcibly single-word, it should not disturb the bus for long periods. Even if it did, we could eventually use one of the wishbone signals (retry) to abort that transaction and serve a more priority one.

 

There's one drawback though: power consumption.

 

Alvie

Share this post


Link to post
Share on other sites

You could also just watch for patterns on the address bus and trigger an interrupt on those. Not only is it a good debug tool but of course you can interrupt on a device writing to a memory location - and without polling the bus - kind of like PCI MSI

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now