Cooperative Multitasking

Microchip TCP/IP Stack

Microchip TCP/IP Stack Help
Cooperative Multitasking

If you implement the TCP/IP stack using a cooperative multitasking approach, you must make periodic calls to task functions to transmit/receive packets and to maintain protocol functionality. To prevent conflicts with the stack, you should write your own custom tasks in a way that will allow them to give up the processor if it's not needed. If you create a protocol or application task with multiple steps, it may be beneficial to divide them up between states. You can then use a global or static variable to track your state, and call that task function periodically to move through the state machine. 

The following example contains a sample application for transferring data from a machine of some type to an external target. It includes a task function called ApplicationTask that has states to wait for button inputs, update the display, and transfer data from the machine. The functions in the example are used to represent other actions: ButtonPressDetected represents the code needed to check for an input from the user, LCDDisplay represents the code needed to update a display on the machine, SampleData gets data from the machine, DataBufferIsFull indicates that the buffer used to hold data samples needs to be sent, and TransferData is a function that writes the data to an open TCP or UDP socket. In between each of these states, the ApplicationTask function returns to the main loop, and the StackTask and StackApplications functions are called. This flow will allow the StackApplications function to maintain any module tasks. The StackTask function will periodically transmit the data from the socket buffers to its destination, which will prevent the transmit buffers from overflowing.

unsigned char gAppState;    // State tracking variable

int main (void)
{
    // Pseudo-initialization function
    InitializeCode();

    // Setup application state
    gAppState = STATE_DISPLAY_MENU;

    // Main Loop
    while (1)
    {
        StackTask();
        StackApplications();
        ApplicationTask();
    }
}

void ApplicationTask (void)
{
    switch (gAppState)
    {
        case STATE_DISPLAY_MENU:
            LCDDisplay (stringMainMenu);
            gAppState = STATE_MAIN_MENU;
            break;
        case STATE_MAIN_MENU:
            if (ButtonPressDetected (BUTTON_1) )    // Check an input
                gAppState = STATE_MONITOR_MACHINERY;
            break;
        case STATE_MONITOR_MACHINERY:
            LCDDisplay (stringTransferringData);
            // Generate or send data
            if (DataBufferIsFull())
            {
                TransferData();
            }
            else
            {
                SampleData();
            }
            if (ButtonPressDetected (BACK_BUTTON) )
                gAppState = STATE_DISPLAY_MENU;
            break;
    }
}

Some of the states in your application may be time based. Suppose, for example, that our sample application needs to send data for 5 seconds every time an input is detected. Stack problems could occur if the application used a delay loop to wait for 5 seconds until it was time to stop, so this functionality should be implemented using the stack's built-in tick timer. When the request to send data is received, the code will get the current tick time using the TickGet function, add enough ticks to make up 5 seconds, save it in a static variable called tickCounter, and then switch to a transmit state. Every time the ApplicationTask function gets called, it will enter this state in the state machine, call TickGet again, and then compare it to the value stored in that static variable. If the current time is later than the initial time plus the delay, the code will restore the display and re-enter the main menu state.

void ApplicationTask (void)
{
    static DWORD tickCounter;
    switch (gAppState)
    {
        case STATE_DISPLAY_MENU:
            LCDDisplay (stringMainMenu);
            gAppState = STATE_MAIN_MENU;
            break;
        case STATE_MAIN_MENU:
            if (ButtonPressDetected (BUTTON_1) )    // Check an input
                gAppState = STATE_MONITOR_MACHINERY;
            break;
        case STATE_MONITOR_MACHINERY:
            LCDDisplay (stringTransferringData);
            // Save the current time, and add 5 seconds to it
            tickCounter = TickGet() + (5 * TICK_SECOND);
            gAppState = STATE_CONTINUE_MONITORING;
            break;
        case STATE_CONTINUE_MONITORING:
            if ((long)(TickGet() - tickCounter) > 0)
                gAppState = STATE_DISPLAY_MENU;
            else
            {
                // Generate or send data
                if (DataBufferIsFull())
                {
                    TransferData();
                }
                else
                {
                    SampleData();
                }
            }
            break;
    }
}

There are three tick timing macros declared to help with delays: TICK_SECOND defines the number of ticks in a second, TICK_MINUTE defines the number of ticks in a minute, and TICK_HOUR defines the number of ticks in an hour. By using the tick timer to implement delays, you can ensure that your code won't block critical functions for too long.

Microchip TCP/IP Stack 5.42.08 - June 15, 2013
Copyright © 2012 Microchip Technology, Inc.  All rights reserved.