Protothreads and locking resources

Using locks in protothreads to lock resources

In an application where I used protothreads, I had the need to control access to a resource of the microcontroller. In this case specifically the SPI bus, but it could be any of the microcontroller's other resources like a timer, UART, I2C etc. My solution was to implement a lock macro that allows your software to lock access to a resource.

Often in a microcontroller application, a resource like the SPI bus is shared between several modules. In a recent application that I developed, this was exactly the case. Specifically there was a module that handled reads and writes to an extternal SPI flash. Some of the reads and writes could potentially take very long, and I used PT_YIELD to allow my application to attend to other tasks as well. Below is an extract of the code:

    while(locLength != 0)
    {
        dprintf(DEBUG_DEBUG,"Length = %d\n",Length);
        if (locLength > 256)
        {        
            for (i = 0; i<256; i++)
            {
                *locdataPtr = MVULPCHW_SPI1_TransferByte(DUMMY_DATA);
                locdataPtr++;
            }            
            locLength -= 256;
            dprintf(DEBUG_DEBUG,"Yielding\n");
            PT_YIELD(pt);
        }
        else
        {
            dprintf(DEBUG_DEBUG,"Finishing\n");
            for (i = 0; i<locLength; i++)
            {
                *locdataPtr = MVULPCHW_SPI1_TransferByte(DUMMY_DATA);
                locdataPtr++;
            }
            locLength = 0;
        }    
    }

You can see from the code that after reading 256 bytes of data, the SPI read routine calls PT_YIELD to service other tasks. The fact that it yields to other tasks, means that other peripherals that uses the SPI could potentially attempt to do transactions on the SPI bus.

For this reason I implement a lock mechanism whereby the software can acquire a lock to the SPI peripheral. Here is the PT_LOCK_ACQUIRE and PT_LOCK_RELEASE macros:

struct pt_lock {
  unsigned int lock;
};

typedef struct pt_lock PT_LOCK;

#define PT_LOCK_INIT(l) (l)->lock = 0

#define PT_LOCK_ACQUIRE(pt, l)    \
  do {                        \
    PT_WAIT_UNTIL(pt, (l)->lock == 0);        \
    (l)->lock = 1;                \
  } while(0)

#define PT_LOCK_RELEASE(l)    (l)->lock = 0

To use the code, a global variable of type PT_LOCK needs to be declared. I declared this in my SPI driver routines. To gain access to the SPI, the software calls PT_LOCK_ACQUIRE. The call to PT_LOCK_ACQUIRE will block until the lock becomes available. When done with the resource, call PT_LOCK_RELEASE again, otherwise the lock remains acquired, and no other part of the application will be able to get access to the lock again. Here is an example of using PT_LOCK:

PT_THREAD(MVUEXTFLASH_Read(PT_HANDLE_PTR pt, const uint8_t* DataPtr, const uint32_t Address, const uint16_t Length))
{
    PT_THREADVAR uint16_t i;
    PT_THREADVAR uint8_t* locdataPtr;
    PT_THREADVAR uint16_t locLength;

    PT_BEGIN(pt);

    dprintf(DEBUG_DEBUG,"Entry ... aqcuire lock ....\n");
    PT_LOCK_ACQUIRE(pt,&MVULPCHW_SPI1_Lock);
    dprintf(DEBUG_DEBUG,"SPI Lock Acquired\n");

    HW_DeselectAllSPICS();
    MVULPCHW_SetupSPI(MVULPCHW_SPICHAN_EXTFLASH);
    HW_FCSSetActive();

    dprintf(DEBUG_DEBUG,"Read Array command\n");

    // Send the read array command
    MVULPCHW_SPI1_TransferByte(READ_ARRAY);
    // Send the Address
    MVULPCHW_SPI1_TransferByte((uint8_t)(Address >> 16));
    MVULPCHW_SPI1_TransferByte((uint8_t)(Address >> 8));
    MVULPCHW_SPI1_TransferByte((uint8_t)(Address));
    MVULPCHW_SPI1_TransferByte(DUMMY_DATA);

    // For all the data
    locdataPtr = (uint8_t*)DataPtr;
    locLength = Length;
    
    while(locLength != 0)
    {
        dprintf(DEBUG_DEBUG,"Length = %d\n",Length);
        if (locLength > 256)
        {        
            for (i = 0; i<256; i++)
            {
                *locdataPtr = MVULPCHW_SPI1_TransferByte(DUMMY_DATA);
                locdataPtr++;
            }            
            locLength -= 256;
            dprintf(DEBUG_DEBUG,"Yielding\n");
            PT_YIELD(pt);
        }
        else
        {
            dprintf(DEBUG_DEBUG,"Finishing\n");
            for (i = 0; i<locLength; i++)
            {
                *locdataPtr = MVULPCHW_SPI1_TransferByte(DUMMY_DATA);
                locdataPtr++;
            }
            locLength = 0;
        }    
    }

    // Deseclect the external flash
    HW_FCSSetInactive();

    dprintf(DEBUG_DEBUG,"Done.\n");
    PT_LOCK_RELEASE(&MVULPCHW_SPI1_Lock);
    dprintf(DEBUG_DEBUG,"Lock released.\n");

    PT_END(pt);
}

Note that after PT_BEGIN, PT_LOCK_AQCUIRE is called. This call will block further execution of the SPI flash read routine until the lock becomes available. When the lock is acquired for the reading routine, the routine does its thing, and at the end, before calling PT_END, PT_LOCK_RELEASE is called again, to release the resource.

In this way you can put a PT_LOCK_AQCUIRE/PT_LOCK_RELEASE pair around areas that access a common resource.

I hope you find this useful in your own applications. Let me know what you think.

armand Friday 18 April 2014 - 11:33 am | | Programming

No comments

(optional field)
(optional field)
Remember personal info?
Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.