Jump to content
spirilis

My time with FreeRTOS on the TM4C

Recommended Posts

Picking this forum for a blog thread on learning the "ropes" of FreeRTOS.  TM4C123 launchpad is my learning board for now, using CCSv6 under Windows, latest FreeRTOS and the GNU GCC compiler that ships with CCS (Linaro).

 

From a fresh download of FreeRTOS, I finally have a working example (not relying on "importing" a CCS example and modifying it - you learn more this way).  Here's main.cpp with all the init + task code:

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_gpio.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/sysctl.h"

void UsrSW1_Monitor(void *);

int main(void) {
    MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ;

    MAP_GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4);
    MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);

    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
    MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD);

    if (pdTRUE != xTaskCreate(UsrSW1_Monitor, "Pushbutton_Monitor", 48, NULL, 3, NULL)) {
        // Crash due to insufficient heap memory?
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2);  // Illuminate R+B for purple
        while(1) ;
    }
    //xTaskCreate(NullTask, "Nothing", 48, (void *)1, 4, NULL);

    vTaskStartScheduler();

    // If FreeRTOS ever exits, illuminate orange (R+G)
    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_3);
    while(1) ;

    return 0;
}

void BlinkyTask_LEDBlink(void *pvParams) {
    unsigned int whichLed = (unsigned int)pvParams;
    uint8_t whichBit = 1 << whichLed;
    uint8_t currentValue = 0;

    while (1) {
        currentValue ^= whichBit;
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue);
        vTaskDelay(250 / portTICK_RATE_MS);
    }
}

void UsrSW1_Monitor(void *pvParams) {
    uint16_t inputShifter = 0;
    bool inputLatched = false;
    const uint32_t USR_SW1 = GPIO_PIN_4;  // PF4
    uint8_t whichLed = 1;
    xTaskHandle blinkerTask;

    // Start blinker task with current whichLed value.
    if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) {
        // Crash due to insufficient heap memory?  Halt scheduler and hold all RGB LEDs to white.
        vTaskSuspendAll();
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
        while(1) ;
    }

    while (1) {
        // Poll USR_SW1 GPIO - active LOW
        if (MAP_GPIOPinRead(GPIO_PORTF_BASE, USR_SW1)) {
            inputShifter <<= 1;
        } else {
            inputShifter <<= 1;
            inputShifter |= 1;
        }

        // Test if button has been pressed or released
        if ( !inputLatched && (inputShifter & 0xF0) == 0xF0 ) {
            // Rotate LED
            whichLed++;
            if (whichLed > 3) {
                whichLed = 1;
            }

            // Kill blinker task
            vTaskDelete(blinkerTask);

            // Switch off all LEDs
            MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, 0);

            // Start new blinker task with new whichLed value.
            if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) {
                // Crash due to insufficient heap memory?  Halt scheduler and hold all RGB LEDs to white.
                vTaskSuspendAll();
                MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
                while(1) ;
            }

            // Latch button press so we don't re-trigger until button has been released
            inputLatched = true;
        }
        if ( inputLatched && (inputShifter & 0xFF) == 0 ) {
            inputLatched = false;  // un-latch USR_SW1
        }

        // Wait 10ms before checking again
        vTaskDelay(10 / portTICK_RATE_MS);
    }
}

Result: Red LED blinks, left pushbutton toggles to blue & green and back to red.  Pressing & holding the pushbutton switches the LED but only once, as the pushbutton task "latches" it.

 

The technique of debouncing a pushbutton using periodic polling + bit shifts and logic tests was something I saw on hackaday recently - http://hackaday.com/2015/12/10/embed-with-elliot-debounce-your-noisy-buttons-part-ii/

I didn't use the same test values but it doesn't matter...

 

Using polling instead of interrupt for the pushbutton is definitely one of those things that is easy & practical only if using an RTOS.

Share this post


Link to post
Share on other sites

Ok, diversion into a small project.  Getting FreeRTOS working in CCS on a TM4C123 LaunchPad from ground zero.  With screenshots.  More of a noob guide or hand-holding for folks using CCS who want to get started quickly.

 

Step 1: Create a new CCS project using the GNU GCC compiler.  Ours is called "rtosnull"

post-15991-0-11534200-1459216483_thumb.png

 

With the project created, we are now going to import FreeRTOS first.  Right-click on the project, and make a New folder:

post-15991-0-85963600-1459216493_thumb.png

 

Call it "FreeRTOS".  Now we'll import the necessary FreeRTOS files into this folder:

 

Right-click on the new FreeRTOS folder, then go Import > Import...

post-15991-0-90805400-1459216500_thumb.png

 

Use Import from File System:

post-15991-0-32838200-1459216508_thumb.png

 

Navigate to your unzipped copy of the latest FreeRTOS source code:

post-15991-0-70847400-1459216515_thumb.png

 

Selecting "Source" and hit OK, you can now peruse the directory structure and choose which files you want to import.  Supposedly "co-routines" aren't used much and we don't need readme.txt, so deselect them.  It's not a problem if you include them though.  On the left side, underneath Source, be sure the entire "include" directory is selected - although you can go in there and uncheck the croutine.h file too.  Or not, it won't hurt anything.

post-15991-0-66583300-1459216523_thumb.png

 

Expand "Source" on the left pane though, and start walking the directory structure down.  In particular we need to choose which of the "portable" folder contents we want - the Memory Manager (Source / portable / MemMang) and the main "port" (Source / portable / GCC / ARM_CM4F in our case).  Within MemMang we're going to select heap_2.c which is a reasonable malloc/free manager for our purposes:

post-15991-0-82015800-1459216530_thumb.png

 

Underneath Source / portable / GCC, go into ARM_CM4F and choose everything (it's only 2 files):

post-15991-0-19451200-1459216539_thumb.png

 

Make sure to UN-CHECK the "Create top-level folder" option in this window, then hit Finish to import all your FreeRTOS files.

 

The FreeRTOS files should now be in your project under that FreeRTOS directory:

post-15991-0-23072100-1459216546_thumb.png

Share this post


Link to post
Share on other sites

With FreeRTOS in our project, we need to make sure the C/C++ compiler is configured to properly search the include and portable folders since FreeRTOS (and our project, soon) depend on it.

 

First, right-click on the project name and go to Properties (Alt+Enter is the shortcut).  In CCS Build > GNU Compiler, let's have a look at the Directories:

post-15991-0-93277700-1459217213_thumb.png

 

By default the only folder searched for #include's is the toolchain's own include directory.  Let's add the FreeRTOS stuff (click the document icon with green plus sign, then click the "Workspace" button on the new popup):

post-15991-0-68042400-1459217229_thumb.png

 

With our workspace's FreeRTOS/include folder selected, hit OK to commit it to the listing.

post-15991-0-71974200-1459217237_thumb.png

 

Then let's add the portable/GCC/ARM_CM4F folder (portmacro.h needs to be in the search path too):

post-15991-0-64406600-1459217244_thumb.png

 

While we're in here, there's a few GCC options we're going to tune.

 

In the GNU Compiler > Optimization section... Optimize for space rather than speed, ensure the "Place each function into its own section" and "Place data items into their own section" is checked (it was by default for me), then for kicks add "Convert floating point constants to single-precision constants" since our Cortex-M4F only has hardware support for single-precision float's anyhow:

post-15991-0-10681900-1459217387_thumb.png

 

Under GNU Compiler > Symbols, these are project-wide #define's that will automatically be defined for each source file as it's built.  While FreeRTOS doesn't need any of these, we will need these 2 new symbols later when we try using the TivaWare driver libraries:

post-15991-0-94019700-1459217395_thumb.png

"gcc" is required to make TivaWare's SysCtlDelay() compile and TARGET_IS_TM4C123_RB1 enables us access to the ROM version of the various TivaWare driverlib calls.  We will get to TivaWare later, but while we're in here it's good to add the symbols ahead of time.

 

Under GNU Linker > Basic, we will enable "garbage collection" for unused sections:

post-15991-0-35782800-1459217403_thumb.png

 

This, in combination with the "Place each function" and "Place data items" into their own section parameter, will substantially reduce the size of our compiled firmware to only include that which is truly needed.

 

The compiler and linker internally produce a tree of symbol links and references, from the ResetISR and main() on down - any functions or data symbols specifically called out or referenced by the code (and referenced by code referenced by that, etc) or linker script will be included in the final code output, and anything not referenced will be "garbage collected" so it doesn't end up in the final firmware binary.

Share this post


Link to post
Share on other sites

To get FreeRTOS into a state where we can compile it, a "FreeRTOSConfig.h" file is required and it looks for it.  We'll create it inside the FreeRTOS/include folder just since we have that folder in our include path already:

 

post-15991-0-93205100-1459217781_thumb.png

 

A reasonable minimum FreeRTOSConfig.h file should include:

/* Per-Project FreeRTOS Configuration */

/*
 * Check all the required application specific macros have been defined.
 * These macros are application specific and (as downloaded) are defined
 * within FreeRTOSConfig.h.
 */

#define configMINIMAL_STACK_SIZE 128 // Idle task stack (in 32-bit words)

#define configMAX_PRIORITIES 8 // Adjustable but I kept it at 8 for kicks.

#define configUSE_PREEMPTION 1

#define configUSE_IDLE_HOOK 0

#define configUSE_TICK_HOOK 0

#define INCLUDE_vTaskPrioritySet 0

#define INCLUDE_uxTaskPriorityGet 0

#define INCLUDE_vTaskDelete 1

#define INCLUDE_vTaskSuspend 1

#define INCLUDE_vTaskDelayUntil 0

#define INCLUDE_vTaskDelay 1

#define configUSE_16_BIT_TICKS 0 // not sure what this is

#define configKERNEL_INTERRUPT_PRIORITY (7 << 5) // Lowest priority for RTOS periodic interrupts

#define configMAX_SYSCALL_INTERRUPT_PRIORITY (1 << 5) // Leaves IRQ priority 0 for any non-RTOS Real Time interrupts

#define configTOTAL_HEAP_SIZE (8 * 1024) // Adjustable - TM4C123 should support at least 24KB heap

#define configCPU_CLOCK_HZ 80000000UL // Full 80MHz clock

#define configTICK_RATE_HZ 1000 // 1ms SysTick ticker

After writing that out to FreeRTOS/include/FreeRTOSConfig.h, let's try a mock build:

post-15991-0-94623100-1459217793_thumb.png

 

With any luck, it'll build cleanly.  It won't do anything of course since our main() has no references to FreeRTOS but at least the FreeRTOS source files themselves build cleanly with our current configuration.

Share this post


Link to post
Share on other sites

Now let's make FreeRTOS actually run.

 

FreeRTOS requires the use of several hardware interrupts/peripherals to function, notably the SysTick feature for its RTOS tick and the software interrupt calls SVCall and PendSV for its internal operation (or rather, how RTOS tasks perform system calls of the kernel).

 

First off, what functions does it provide for these interrupts?  Check the portable port.c file:

post-15991-0-56946800-1459217959_thumb.png

 

All ARM projects include a special .c file for the Interrupt Vector Table and C init runtime functions (reset vector, Fault, NMI, etc).  This is "tm4c123gh6pm_startup_ccs_gcc.c" in our case.

 

Let's declare those interrupt handler functions with the "extern" keyword in our vector table file:

post-15991-0-41400300-1459217967_thumb.png

 

Then, place those function names (pointers to those functions) in the correct slots in the interrupt vector table itself:

post-15991-0-00484800-1459217974_thumb.png

 

With the interrupt vector table properly configured to be FreeRTOS-aware, we now point our efforts at main.c:

post-15991-0-52308000-1459218162_thumb.png

 

Our simple main.c includes the FreeRTOS.h and task.h files (so it has access to the Task-related function prototypes), and for our first task, we create an instance of a dummy function that just continually sleeps (but in an RTOS-aware way, using vTaskDelay() which allows the RTOS to "swap out" our task and execute others in its absence).

 

At this point, it should build:

post-15991-0-34945200-1459218266_thumb.png

Share this post


Link to post
Share on other sites

Now at this point, unfortunately, I found myself outside my element with respect to CCS and debug configurations et al.  For some odd reason, I don't have a valid Debug configuration when I create a new Tiva GCC project.  So I had to figure out how to create one (although I don't think I have the procedure correct yet)-

 

Go to Debug configurations-

post-15991-0-61654300-1459218475_thumb.png

 

Under "Code Composer Studio" I didn't have any, so I created one (default name "New_configuration"):

post-15991-0-87977900-1459218484_thumb.png

 

Apparently you need a .ccxml file.  I just browsed to my TivaWare install and found one from the ek-tm4cgxl board examples:

post-15991-0-68181600-1459218495_thumb.png

 

Looked like I was on the right track, showing CORTEX_M4 for the target:

post-15991-0-96721300-1459218504_thumb.png

 

I'm guessing it needs to know about my project so it knows what to upload:

post-15991-0-21905300-1459218514_thumb.png

 

post-15991-0-06970200-1459218523_thumb.png

 

I found out the hard way after a lot of banging my head that you should have it reset the CPU on program load:

post-15991-0-25677600-1459218532_thumb.png

 

By default, it will auto-run but breakpoint at main, which is the typical behavior:

post-15991-0-46180200-1459218540_thumb.png

 

And to use this new config, for some reason I can't use it when I just hit the debug button.... but if I add it to the "favorites" I can select it in the debug icon's dropdown menu:

post-15991-0-37871200-1459218548_thumb.png

 

I hope to revise this post once I figure out WTF was wrong with my initial project creation process, or see if there's a "correct" way to redo the debug config.

Share this post


Link to post
Share on other sites

Running the RTOS project:

post-15991-0-29205700-1459219707_thumb.png

 

CCS Debug mode should initialize the ICDI debugger and upload the code, then immediately try running the firmware while breakpointing at main():

post-15991-0-85141900-1459219746_thumb.png

 

Go ahead and hit the play button to continue running (without stepping):

post-15991-0-55033200-1459219761_thumb.png

 

Then after a second or two, try interrupting it to see where it ends up stopped:

post-15991-0-33797900-1459219807_thumb.png

 

post-15991-0-33111400-1459219822_thumb.png

And voila, we ended up in the middle of one of its internal cleanup functions spawned off the idle task.  At least we're not off in lala land or FaultISR(), instead we're running a validly configured RTOS.  Now this completes the basic FreeRTOS setup, in the next installment (to be continued soon), I'll import TivaWare's driverlib and start doing something useful.  Note that at this stage, FreeRTOS still thinks it's SysTick'ing off an 80MHz clock (due to our current FreeRTOSConfig.h contents) but it's doing no such thing because the CPU is running at the default power-on speed, which is something like 16MHz or 4MHz or whatever.  So all vTaskDelay's are woefully inaccurate right now.  This will be remedied once we use TivaWare driverlib to configure the main clock.

Share this post


Link to post
Share on other sites

Liking this Spirilis!

 

In CCS, it has been that way. If you create a new project, you have to create a new debug profile. 

Ah ok.  Do you know how I can make my new profile the default for when I press the debug button?  Right now it's still complaining about a missing .ccxml file for the stock freertos_demo in my workspace which I've long since deleted...

Share this post


Link to post
Share on other sites

Importing TivaWare-

 

Right-click on the project, go Import > Import... and then File System.  Browse to your TivaWare install (C:\ti\TivaWare_C_Series-2.1.2.111 in my case):

post-15991-0-90003700-1459256886_thumb.png

 

We are going to import the "inc" and "driverlib" directories and this time, we WILL have "Create top level folder" checked:

post-15991-0-09687500-1459256892_thumb.png

 

After inc is imported, go back through the project right-click > Import > Import... file system, TivaWare, and import driverlib, but don't include any of its sub-folders:

post-15991-0-54325300-1459256896_thumb.png

 

At this point you have inc and driverlib:

post-15991-0-89176400-1459256900_thumb.png

 

Now, TivaWare examples typically have you doing #include "inc/tm4c123gh6pm.h" or #include "driverlib/can.h" or whatever.  In order for that to work correctly, our workspace's main directory needs to be in the GNU Compiler's Include path.

 

Back to our old friend, the Preferences (Alt+Enter or right-click project and hit "Properties"):

post-15991-0-48676800-1459257178_thumb.png

 

Add a new entry, selecting Workspace, then our project's name (nothing underneath):

post-15991-0-53908500-1459257182_thumb.png

 

Hit OK, it fills in the variable path to the project:

post-15991-0-87068100-1459257186_thumb.png

 

And here's our final include directory list:

post-15991-0-74425800-1459257191_thumb.png

 

For the sake of completeness, you COULD do the same thing we did with the FreeRTOS source files - create a "TivaWare" folder under the project, right-click on that and import inc/ and driverlib/ into that folder, then add <project location>/TivaWare to the GNU Compiler Include directory paths instead of the main project folder (or in addition to, since it shouldn't hurt).  That way TivaWare and all its components are compartmentalized into their own folder which can make the project easier to inspect if you add any more TivaWare features to it (usblib, nfclib, etc).  We won't do that for this simple example though.

Share this post


Link to post
Share on other sites

TivaWare examples usually have a lot of #include's.  This looks like a decent minimal list for doing sysctl configuration (e.g. system clock and enabling peripherals) and GPIO:

post-15991-0-38865300-1459258262_thumb.png

 

The TARGET_IS_TM4C123_RB1 #define we put in the project's symbols enables correct use of the MAP_ and ROM_ version of the various driverlib calls.  Proof is in driverlib/rom.h:

//*****************************************************************************
//
// Macros for calling ROM functions in the ADC API.
//
//*****************************************************************************
#if defined(TARGET_IS_TM4C123_RA1) ||                                         \
    defined(TARGET_IS_TM4C123_RA3) ||                                         \
    defined(TARGET_IS_TM4C123_RB1) ||                                         \
    defined(TARGET_IS_TM4C123_RB2) ||                                         \
    defined(TARGET_IS_TM4C129_RA0) ||                                         \
    defined(TARGET_IS_TM4C129_RA1) ||                                         \
    defined(TARGET_IS_TM4C129_RA2)
#define ROM_ADCSequenceDataGet                                                \
        ((int32_t (*)(uint32_t ui32Base,                                      \
                      uint32_t ui32SequenceNum,                               \
                      uint32_t *pui32Buffer))ROM_ADCTABLE[0])
#endif
#if defined(TARGET_IS_TM4C123_RA1) ||                                         \
    defined(TARGET_IS_TM4C123_RA3) ||                                         \
    defined(TARGET_IS_TM4C123_RB1) ||                                         \
    defined(TARGET_IS_TM4C123_RB2) ||                                         \
    defined(TARGET_IS_TM4C129_RA0) ||                                         \
    defined(TARGET_IS_TM4C129_RA1) ||                                         \
    defined(TARGET_IS_TM4C129_RA2)
#define ROM_ADCIntDisable                                                     \
        ((void (*)(uint32_t ui32Base,                                         \
                   uint32_t ui32SequenceNum))ROM_ADCTABLE[1])
#endif

rom_map.h wraps those calls in MAP_ prefixes so that if it's discovered that a certain Tiva silicon release had a bug in its ROM driver code, updated TivaWare editions may choose to use a source-compiled version of the driverlib function instead of the ROM_ version.  We'll use MAP_ for all our driverlib calls.  There is one exception IIRC, I think it's IntRegister(), that can never use the ROM version but we aren't going to touch it during this example.

 

Note that the exact TARGET define, e.g. TARGET_IS_TM4C123_RB1, should actually be doctored to coincide with the exact silicon you're using.  RB2 is the very latest revision as of March 2016, but if you're building this for an earlier release of the Tiva chip, it might be prudent to investigate the actual CPU revision you're running (see the chip's datasheet - should be gleaned from reading the markings on the chip but you can figure this out programmatically) and use the correct TARGET_IS_TM4C123_Rxy setting.  (I actually think mine is older than RB1, but it's not a big problem for this example).  Anyway, the available versions can be found by examining driverlib/rom.h and then look at the Tiva chip's datasheet + errata sheet for more details on versions, bugs, etc.

 

Here's our original main.c, reworked to use TivaWare just to properly configure the CPU clock:

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>

#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_gpio.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/sysctl.h"

void NullTaskFunc(void *);  // prototype so we can include it in main while the code is underneath

int main(void) {
    MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);
    
    // Prototype for xTaskCreate:
    //
    //  BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
    //                          const char * const pcName,
    //                          uint16_t usStackDepth,
    //                          void *pvParameters,
    //                          UBaseType_t uxPriority,
    //                          TaskHandle_t *pvCreatedTask);
    if (pdTRUE != xTaskCreate(NullTaskFunc, "Null Task", 32, NULL, 4, NULL)) {
        while (1) ;  // Oh no!  Must not have had enough memory to create the task.
    }
    
    vTaskStartScheduler();  // Start FreeRTOS!

    // Should never get here since the RTOS should never "exit".
    while(1) ;
    return 0;
}

// Our RTOS "task" - does absolute jack squat
void NullTaskFunc(void *pvParameters) {
    while (1) {
        vTaskDelay(10000); // With this task always delaying, the RTOS Idle Task runs almost all the time.
    }
}

This configures the PLL with correct crystal speed, enabling the crystal oscillator, to land us at 80MHz.

 

Note that in main.c, if you forgot to include stdbool.h, some of the driverlib .h files will complain:

post-15991-0-43456500-1459257959_thumb.png

 

Also, driverlib imports a file called "epi_workaround_ccs.s" which won't compile under GCC, so we should delete it:

post-15991-0-09335100-1459258026_thumb.png

 

post-15991-0-36169100-1459258077_thumb.png

 

Now it builds correctly:

post-15991-0-99688400-1459264483_thumb.png

 

Our FreeRTOS project thus far doesn't do anything visibly different from the last time we built it, besides the CPU clock running at the correct frequency, so we'll skip running it for now.  Next, we're going to create another task that blinks an LED, and I'll include logic analyzer output validating the cadence of the blink.

Share this post


Link to post
Share on other sites

Just realized I had my clock off by a factor of 2, it should be SYSCTL_SYSDIV_2_5 not SYSCTL_SYSDIV_5.  Updating the examples above...

 

And now our blinky example...

 

Building off main.c above, we'll add some GPIO init inside main():

    // For LED blinky task - initialize GPIO port F and then pin #1 (red) for output:
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); // activate internal bus clocking for GPIO port F
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; // busy-wait until GPIOF's bus clock is ready
    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1); // PF_1 as output
    // doesn't need too much drive strength as the RGB LEDs on the TM4C123 launchpad are switched via N-type transistors
    MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD);
    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0); // off by default

Then, code for an example task (this is outside of main()):

// The time-honored blinky.
void BlinkMyLED(void *pvParameters) {
    unsigned int whichLed = (unsigned int)pvParameters; /* While pvParameters is technically a pointer, a pointer is nothing
                                                         * more than an unsigned integer of size equal to the architecture's
                                                         * memory address bus width, which is 32-bits in ARM.  We're abusing
                                                         * the parameter then to hold a simple integer value.  Could also have
                                                         * used this as a pointer to a memory location holding the value, but
                                                         * our method uses less memory.
                                                         */
    const uint8_t whichBit = 1 << whichLed; // TivaWare GPIO calls require the pin# as a binary bitmask, not a simple number.
    // Alternately, we could have passed the bitmask into pvParameters instead of a simple number.
    uint8_t currentValue = 0;

    while (1) {
        currentValue ^= whichBit; // XOR keeps flipping the bit on / off alternately each time this runs.
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue);

        vTaskDelay(125 / portTICK_RATE_MS);  // Suspend this task (so others may run) for 125ms or as close as we can get with the current RTOS tick setting.
    }
    // No way to kill this blinky task unless another task has an xTaskHandle reference to it and can use vTaskDelete() to purge it.
}

Finally, back inside main(), we'll create our LED blinker task, pointing it to LED #1 (the task is already hardcoded to use GPIO_PORTF_BASE so that's not changeable but in theory, we could run multiple blinker tasks operating on different LEDs):

    if (pdTRUE != xTaskCreate(BlinkMyLED, "Blinker", 32, (void *)1, 4, NULL)) { // (void *)1 is our pvParameters for our task func specifying PF_1
        while (1) ; // error creating task, out of memory?
    }

Share this post


Link to post
Share on other sites

Wrapping it together, here's our full main.c:

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_gpio.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/sysctl.h"

void NullTaskFunc(void *);  // prototype so we can include it in main while the code is underneath
void BlinkMyLED(void *);

int main(void) {
    MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);

    // Prototype for xTaskCreate:
    //
    //  BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
    //                          const char * const pcName,
    //                          uint16_t usStackDepth,
    //                          void *pvParameters,
    //                          UBaseType_t uxPriority,
    //                          TaskHandle_t *pvCreatedTask);
    if (pdTRUE != xTaskCreate(NullTaskFunc, "Null Task", 32, NULL, 4, NULL)) {
        while (1) ;  // Oh no!  Must not have had enough memory to create the task.
    }
    
    // For LED blinky task - initialize GPIO port F and then pin #1 (red) for output:
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); // activate internal bus clocking for GPIO port F
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; // busy-wait until GPIOF's bus clock is ready
    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1); // PF_1 as output
    // doesn't need too much drive strength as the RGB LEDs on the TM4C123 launchpad are switched via N-type transistors
    MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD);
    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0); // off by default

    if (pdTRUE != xTaskCreate(BlinkMyLED, "Blinker", 32, (void *)1, 4, NULL)) { // (void *)1 is our pvParameters for our task func specifying PF_1
        while (1) ; // error creating task, out of memory?
    }

    vTaskStartScheduler();  // Start FreeRTOS!

    // Should never get here since the RTOS should never "exit".
    while(1) ;
    return 0;
}

// Our RTOS "task" - does absolute jack squat
void NullTaskFunc(void *pvParameters) {
    while (1) {
        vTaskDelay(10000); // With this task always delaying, the RTOS Idle Task runs almost all the time.
    }
}

// The time-honored blinky.
void BlinkMyLED(void *pvParameters) {
    unsigned int whichLed = (unsigned int)pvParameters; /* While pvParameters is technically a pointer, a pointer is nothing
                                                         * more than an unsigned integer of size equal to the architecture's
                                                         * memory address bus width, which is 32-bits in ARM.  We're abusing
                                                         * the parameter then to hold a simple integer value.  Could also have
                                                         * used this as a pointer to a memory location holding the value, but
                                                         * our method uses less memory.
                                                         */
    const uint8_t whichBit = 1 << whichLed; // TivaWare GPIO calls require the pin# as a binary bitmask, not a simple number.
    // Alternately, we could have passed the bitmask into pvParameters instead of a simple number.
    uint8_t currentValue = 0;

    while (1) {
        currentValue ^= whichBit; // XOR keeps flipping the bit on / off alternately each time this runs.
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue);

        vTaskDelay(125 / portTICK_RATE_MS);  // Suspend this task (so others may run) for 125ms or as close as we can get with the current RTOS tick setting.
    }
    // No way to kill this blinky task unless another task has an xTaskHandle reference to it and can use vTaskDelete() to purge it.
}

Built:

post-15991-0-67893400-1459264855_thumb.png

 

Project running:

post-15991-0-18656700-1459265062_thumb.png

 

Video:

 

 

Saleae Logic16 sample:

post-15991-0-49337700-1459265023_thumb.png

Share this post


Link to post
Share on other sites

RTOS concurrency and communications peripherals.  How about we take 2 tasks and have them both spam the SPI peripheral on the Tiva with output?

 

main.c:

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_gpio.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/ssi.h" // for SPI
#include <string.h> // for strlen()

void Task_SPI_Transfer(void *);

int main(void) {
    MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);  // SSI2 on PB4/PB6/PB7
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // Need this to configure PB for SSI usage
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)) ;
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) ;

    // Configure PB4, PB6, PB7 as SSI pins
    MAP_GPIOPinTypeSSI(GPIO_PORTB_BASE, (GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7) );
    /* Sadly, it's not just enough to configure GPIO pins for the correct peripheral.  You also have to set a register called GPIOPCTL for these.
     *
     * Register 22: GPIO Port Control (GPIOPCTL), offset 0x52C
     * The GPIOPCTL register is used in conjunction with the GPIOAFSEL register and selects the specific
     * peripheral signal for each GPIO pin when using the alternate function mode. Most bits in the
     * GPIOAFSEL register are cleared on reset, therefore most GPIO pins are configured as GPIOs by
     * default. When a bit is set in the GPIOAFSEL register, the corresponding GPIO signal is controlled
     * by an associated peripheral. The GPIOPCTL register selects one out of a set of peripheral functions
     * for each GPIO, providing additional flexibility in signal definition.
     */
    MAP_GPIOPinConfigure(GPIO_PB4_SSI2CLK);
    MAP_GPIOPinConfigure(GPIO_PB6_SSI2RX);
    MAP_GPIOPinConfigure(GPIO_PB7_SSI2TX);

    // Init SSI2
    MAP_SSIClockSourceSet(SSI2_BASE, SSI_CLOCK_SYSTEM); // System CPU clock used
    MAP_SSIConfigSetExpClk(SSI2_BASE, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 8000000, 8); // 8MHz SPI, mode 0, 8 bits
    MAP_SSIEnable(SSI2_BASE);  // SPI peripheral is now live.

    // Two EQUAL PRIORITY tasks will continously duke it out trying to send SPI data.
    // Prototype for xTaskCreate:
    //  BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
    //                          const char * const pcName,
    //                          uint16_t usStackDepth,
    //                          void *pvParameters,
    //                          UBaseType_t uxPriority,
    //                          TaskHandle_t *pvCreatedTask);

    xTaskCreate(Task_SPI_Transfer, "Task1", 32, "The quick brown fox", 4, NULL);
    xTaskCreate(Task_SPI_Transfer, "Task2", 32, "bluehash runs a great forum", 4, NULL);

    vTaskStartScheduler(); // Start FreeRTOS!
    while(1) ; // Shouldn't get here.
    return 0;
}


void Task_SPI_Transfer(void *pvParameters) {
    const char *str = (const char *)pvParameters;
    const size_t slen = strlen(str);
    size_t i = 0;

    while (1) {
        // Wait for SPI peripheral to stop transmitting
        while (MAP_SSIBusy(SSI2_BASE)) ;
        // Stuff the FIFO full
        while (MAP_SSIDataPutNonBlocking(SSI2_BASE, str[i]) > 0) {
            i++; // Note: do not be tempted to use "str[i++]" in the while() statement for this.  It will increment 'i' even if the call fails.
            if (i >= slen) {
                i = 0;
            }
        }
    }
}

After capturing 100ms of the output, my Saleae Logic16's software did a protocol analysis of the SPI data and after mangling it a bit with GNU awk, here's what it looks like (apostrophes are meant to be spaces):

b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u 

Besides the endless accolades it provides for @@bluehash, of note is that you can tell when the RTOS tick "task switch" occurred:

 

b l u e h a s h ' r u n s ' a ' g r e a t ' f T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n

 

Now that's no good!  Even if the point was to spam the SPI bus with data, doing so in coherent blocks/sentences would be preferable.  Those sentences could be coherent blocks of data or control information for an Ethernet peripheral, SPI-attached radio (CC3100, nRF24L01+, etc) or who knows.  Having the data cut in the middle of a logically-contiguous block could cause corruption (especially if separate SPI Chip Select lines are used - but - the task never had a chance to complete its SPI I/O and then deselect the intended peripheral's SPI CS line before the next task goes to town).  Moreover, electrical problems/short circuits could result if multiple SPI slave devices try driving the MISO line due to more than 1 having their Chip Select line selected.  How do we manage this in an RTOS environment?

 

One way is using Mutual Exclusion - mutex's - and having a common mutex for an SPI peripheral.  Another way, would be a "gatekeeper" task - a task specifically designed to perform SPI communication on everyone else's behalf, using RTOS queues to pass the buffers back and forth.  This gatekeeper task would take a single request off the queue, then run it to completion, returning the results in a private queue encapsulated inside the request for the original caller to digest - all the while, NOT reading from the queue so that other tasks attempting to use the SPI bus will halt.  I am going to try the Mutex idea first, then the gatekeeper task.

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

×