A couple of days ago there was a short discussion on the task scheduler library ( http://arduino.cc/forum/index.php/topic,130917.0.html ).
One aspect of the library which hasn't been implemented yet was a mutex or similar form of mutual exclusion; as the task scheduler library is something that I would see myself using, and keeping with the spirit of open source I thought I would give one a try. This library is a little strange, I did not want to modify the task scheduler until I had a stable idea, also to make it easier for people to test without rummaging through the Arduino core. As a result the library is fully compatible with UNO and other 8 bitters.
Also, as I do not have a DUE to test anything, I have provided a sketch at the bottom which would be great if someone could collect the serial output for me
It has fully global counters, It doesn't suffer from the problems known to things like COUNTER.
Locks are pretty much inaccessible to users, they cannot be manually created, compile time errors will occur if attempted.
Locks are implemented as types rather than values, this guarantees a lock is globally unique, not just to a translation unit.
An update I'll have shortly allows mutex data to be non-volatile if you guarantee it is exclusive to the task scheduler ( not shared with ISR's ).
Here is the basic layout:
atomic_mode can be one of the three options below:
- m_Restore
- m_Force
- m_None
HMUTEX is a handle to a mutex.
HLOCK is a handle to a lock.
HTASK is a handle to a task.
User interface:
-
createMutex( atomic_mode )
Creates a mutex based on a blocking mode.
Returns a handle to a mutex. -
createMutexEx( atomic_mode, lock_mode )
Creates a mutex based on a blocking mode, also specify what sort of lock to use.
Locks can be either singular or recursive, using these input values 'l_Simple' or 'l_Recursive'
Returns a handle to a mutex. -
duplicateHandle( mutex_handle, atomic_mode )
Copies a handle, more specifically, allows a lock to be used by more than one mutex with different blocking methods.
Returns a handle to a mutex. -
acquireMutex( mutex_handle )
Use to lock a mutex, returns a boolean specifying success or failure. -
releaseMutex( mutex_handle )
Use to unlock a mutex, returns a boolean specifying success or failure. -
getOwner( mutex_handle )
Returns the ID of the task that has locked the mutex. LOCK_FREE is returned if the mutex is unlocked. -
getMutexMode( mutex_handle )
Returns the blocking method of the Mutex 'mutex_handle' refers to. -
registerTask( name )
Use to notify the system of functions that are independent tasks ( interrupts & scheduler tasks ).
All registered tasks must be marked with the function modifier 'TASK'
This returns a handle to a task( they are not useful yet ). -
isOwner( mutex_handle )
Returns true if the calling task owns the mutex. -
getTaskID()
Returns the ID of the calling task. -
safeCall()
Allows calling task functions from within a task.
I'll have more time after work to be clearer in my explanation if needed.
Here is a test sketch, I don't have a DUE so I could only compile it. ( I would just like to see the output of the task ID's, or if this will even run in its current state )
#include "Mutex.h"
#ifdef __arm__
#include <Scheduler.h>
#endif
//Two mutex's created, and two handles duplicated.
HMUTEX h_MutexA0 = createMutex( m_Force ); //Uses atomic blocking.
HMUTEX h_MutexA1 = duplicateHandle( h_MutexA0, m_Restore ); //Uses atomic blocking in restore mode ( initial interrupt mode may be unknown ).
HMUTEX h_MutexB0 = createMutex( m_None ); //No atomic blocking. ( read TC0_Handler )
HMUTEX h_MutexB1 = duplicateHandle( h_MutexB0, m_Force ); //Uses atomic blocking.
HTASK h_TaskA = registerTask( loop_1 );
HTASK h_TaskB = registerTask( TC0_Handler );
void setup(){
#ifdef __arm__
Scheduler.startLoop( loop_1 );
//Need to setup stuff for TC0_Handler, I do not know how to setup ARM interrupts yet.
#endif
Serial.begin(9600);
}
void loop(){
Serial.println( "================================" );
if( acquireMutex( h_MutexA0 ) ){
//some shared memory operation.
releaseMutex( h_MutexA0 );
}
Serial.print( "loop ID: " );
Serial.println( getTaskID(), HEX );
foo();
//Simulate scheduler call for AVR8
#ifndef __arm__
loop_1();
#endif
//Call task within another task
Serial.print( "safeCall a task within task: " );
Serial.println( getTaskID(), HEX );
safeCall( loop_1 );
delay(3000);
}
_TASK_ void loop_1(){
if( acquireMutex( h_MutexA1 ) ){
releaseMutex( h_MutexA1 );
}
Serial.print( "loop_1 ID: " );
Serial.println( getTaskID(), HEX );
foo();
#ifdef __arm__
delay(500);
yield();
#endif
}
_TASK_ void TC0_Handler(){
//h_MutexB0 does not use atomic blocking, i'm assuming like the AVR, interrupts are disabled on entry to an isr ( could be wrong, just for example ).
if( acquireMutex( h_MutexB0 ) ){
releaseMutex( h_MutexB0 );
}
delay(10);
Serial.print( "IRQ_Handler ID: " );
Serial.println( getTaskID(), HEX );
}
void foo(){
if( acquireMutex( h_MutexB1 ) ){
releaseMutex( h_MutexB1 );
}
Serial.print( "foo ID: " );
Serial.println( getTaskID(), HEX );
}
It requires my AtomicBlock library which is covered in detail here: Replacement for AVR libc ATOMIC_BLOCK macros, now for DUE and other platforms. - Libraries - Arduino Forum
Files are available below or on Google code here if you aren't signed into Arduino.
Note: Google code documents are marked with revision numbers, the test code above will need the include changed to "Mutex_01b.h", or you can change the file names to suit.
AtomicBlock_121b.h (21 KB)
Mutex.h (11.2 KB)
Mutex.cpp (92 Bytes)