A while back I bought a Openbench Logic Sniffer from Dangerous Prototypes. Whilst it kind-of worked it was most definitely alpha and really not much practical use - no big deal considering the low price. However everything is field-upgradable so I put it to one side and more or less forgot about it. Over the last few days I've revisited the project and upgraded everything, and it's turning into a fantastic little tool. The first thing of note is that the FPGA core has been completely rewritten by doggsbody, who almost unbelievably has managed to get most of the functionality of a HP16550A logic analyser into the FPGA, although the user interface hasn't yet been extended to allow access to all the new goodness.
The original flakiness between the FPGA and the PIC processor used to glue everything together seems to have been solved as well. The real icing on the cake is there's also a new client to drive it all - the old SUMP client was really getting a bit long in the tooth, and there's a new and much superior version that Jawi has produced. Unfortunately although the client is written in Java, it didn't work Solaris as it didn't include the Solaris version of the RXTX JNI library. Jawi very kindly helped me get it all working, and even better he's integrated Solaris support into the next release as well, so it will just work out of the box. There's still more work to be done, for example, from what I can gather the 200MHz sampling mode still doesn't work properly, but having said that the progress since I last took a serious look a the project is very impressive.
The last thing of note is there's now a buffer wing available that allows you to use all of the available 32 channels with 5V inputs - I'm very tempted to get one :-)
It's very common for Arduino sketches to use the
delay() library routine to control timing when performing time-related operations such as LED animations. Unfortunately
delay() is toxic if you need to do more than one thing at once - for example animating more than one LED strip - as calling
delay() just makes the CPU spin until the required amount of time has passed, and obviously nothing else can happen until the
delay() call returns. The same is true for anything that uses any kind of spin-loop to wait for an event, for example polling a switch or a rotary encoder until it changes state (commonly used as a way of debouncing). The Arduino ecosphere is full of such example code and to be blunt all of it is completely useless - unless of course all you want do is dedicate your whole microcontroller to dealing with a single IO device.
It is possible to partially work around this issue by using timer interrupts to trigger actions, because the interrupt service routine will be called even if
delay() is currently executing. However interrupts have their issues as well. The issues around interrupt service routine overhead and reentrancy are fairly widely understood. The issues around atomicity are less well understood, even in commercial products. Simply declaring a variable as
volatile isn't sufficient - except for very simple cases you need to be sure that interrupts are disabled during all access to variables that are shared between ISR and non-ISR code. And if you are accessing several variables, or a structure, you need to make sure interrupts are disabled around the entire block.
- A RTOS may help manage shared software resources by providing features such as thread-safe queues and lists, but it can't really solve the problems related to shared hardware resources. For example, when several tasks need to access the SPI bus they each need to complete their work before relinquishing the bus. If a task is communicating with a peripheral on the bus, scheduling another task in the middle of the operation is not possible so RTOS preemption support is irrelevant - preemption needs to be disabled anyway until the current task has completed its bus transaction.
- Supporting preemption means that tasks can potentially be suspended and resumed at any point. This means that the complete state of a task needs to be saved somewhere, including all the processor registers and the current stack for the thread. This overhead is significant - the ATMega168 only has 1Kb of SRAM, and the ATMega328P has 2K. Reports say that as few as 3-4 FreeRTOS tasks can be run on Arduino-class microcontrollers.
- avr-libc, the core of the runtime system, isn't thread-safe, so if you use a RTOS you need either to lock around every call into avr-libc or you'll need a complete replacement for avr-libc. The situation with commonly-used Arduino libraries is just as bad - they weren't written with threads in mind, and will almost certainly break in strange and mysterious ways if used with a RTOS.
All in all, a RTOS isn't a good solution for severely constrained platforms such as the ATMega, yet we really need something to help us manage concurrency. That was the impetus behind the creation of my task library. It has the following features:
- Non-preemptive, cooperative scheduler.
- Fixed, priority scheduling governed by task list ordering.
- Task list fixed at compile time.
- Small code size - 28 bytes for the Task class and 116 bytes for the scheduler.
- Low RAM requirements - 4 bytes + 2 bytes per task.
- No thread stacks required - each task runs to completion, so the stack is fully unwound before task switching.
Whilst the task manager is undoubtedly much more limited than a RTOS, the limitations are ones that can usually be lived with. In compensation it becomes possible to schedule many tens of tasks even on a severely constrained platform such as the ATMega. As long as we can break the workload into small enough chunks, we can just schedule the tasks in a cooperative fashion. By providing priority scheduling we can ensure that even when temporarily overloaded the system will still remain responsive by scheduling the most important tasks first, and deferring the lower-priority tasks until the load drops again. In any case, preemption is no help if the system can't actually keep up with the rate of events it is being expected to handle.
So, in summary don't use
delay() when there are better alternatives available! :-)