Jump to content
spirilis

My time with FreeRTOS on the TM4C

Recommended Posts

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?

 

This is where it gets really interesting! Any RTOS can turn out a clean-looking led blinking program. The true test is how it deals with shared resources. I'm looking forward to the next few posts! :)

Share this post


Link to post
Share on other sites

So far, a few changes:

 

1. FreeRTOSConfig.h has a whole lot more options than I had listed, some of which you need in order to use mutexes/queues/etc.  A mutex is just a queue underneath the hood FYI.

/* 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.
 */




// Mandatory
#define configMINIMAL_STACK_SIZE 32 // 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 (24 * 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



// Optional stuff
#define configUSE_MUTEXES 1
// #define configUSE_RECURSIVE_MUTEXES 0
// #define configUSE_COUNTING_SEMAPHORES 0
// #define configUSE_QUEUE_SETS 0
// #define configUSE_TIMERS 0
// #define configUSE_ALTERNATIVE_API 0
// #define configUSE_TRACE_FACILITY 0
// #define configUSE_CO_ROUTINES 0
// #define configUSE_STATS_FORMATTING_FUNCTIONS 0
// #define configUSE_TICKLESS_IDLE 0
// #define configUSE_APPLICATION_TASK_TAG 0
// #define configNUM_THREAD_LOCAL_STORAGE_POINTERS 0
// #define configGENERATE_RUN_TIME_STATS 0
// #define configUSE_NEWLIB_REENTRANT 0
// #define configUSE_TASK_NOTIFICATIONS 0
// #define configUSE_PORT_OPTIMIZED_TASK_SELECTION 0
// #define configCHECK_FOR_STACK_OVERFLOW 0
// #define configIDLE_SHOULD_YIELD 0
// #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 0
// #define configMAX_TASK_NAME_LEN 0
// #define configTIMER_TASK_STACK_DEPTH 0
// #define configTIMER_TASK_PRIORITY 0
// #define configTIMER_QUEUE_LENGTH 0

// #define INCLUDE_xTimerPendFunctionCall 1
// #define INCLUDE_xTaskGetSchedulerState 1
// #define INCLUDE_xSemaphoreGetMutexHolder 1
// #define INCLUDE_xTaskGetIdleTaskHandle 1
// #define INCLUDE_eTaskGetState 1
// #define INCLUDE_xTaskResumeFromISR 1
// #define INCLUDE_xTaskGetIdleTaskHandle 1
// #define INCLUDE_pcTaskGetTaskName 1
// #define INCLUDE_uxTaskGetStackHighWaterMark 1
// #define INCLUDE_xTaskGetCurrentTaskHandle 1
// #define INCLUDE_xTaskGetSchedulerState 1
// #define INCLUDE_xTimerGetTimerDaemonTaskHandle 1

2. Here's my main.c, doctored up to utilize a mutex:

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#include <queue.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()

// Data structure featuring a string and mutex for arbitrating SPI I/O.
typedef struct {
    xSemaphoreHandle spiMutex; // this is just a pointer FYI.
    char * str;
} spiTaskParams;

// Declaring the space for these inside main() causes corruption, so make them global?
spiTaskParams task1_Params, task2_Params;


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) );
    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.


    xSemaphoreHandle spiMutex;
    spiMutex = xSemaphoreCreateMutex();

    task1_Params.spiMutex = spiMutex;
    task1_Params.str = "The quick brown fox";
    xTaskCreate(Task_SPI_Transfer, "Task1", 32, (void *)&task1_Params, 4, NULL);

    task2_Params.spiMutex = spiMutex;
    task2_Params.str = "bluehash runs a great forum";
    xTaskCreate(Task_SPI_Transfer, "Task2", 32, (void *)&task2_Params, 4, NULL);

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


void Task_SPI_Transfer(void *pvParameters) {
    spiTaskParams *params = (spiTaskParams *)pvParameters;

    const char *str = params->str;
    const size_t slen = strlen(str);
    size_t i = 0;
    xSemaphoreHandle spiMutex = params->spiMutex;

    while (1) {
        xSemaphoreTake(spiMutex, portMAX_DELAY); // Suspend this task indefinitely until the mutex is available
        { // braces not necessary but makes the code between the mutex take/give easy to identify
            // 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;
                }
            }
        }
        xSemaphoreGive(spiMutex);
    }
}

Quick discussion:

The pvParameters given to the task is no longer a simple data type - it's now a struct containing 2 members, an xSemaphoreHandle (which is ultimately just a void * pointer that points to a queue structure) and a char * pointer to the string this task will send over SPI.  Since this is a struct, it must have some memory allocated to it.  I've declared the struct's for both tasks global because when I declared them inside main(), the pointers got corrupted!  Not sure why.

 

In any case, both tasks are receiving separate parameter structures but the spiMutex members are set to the same mutex, so they should be able to exclude one another.

 

Here's the output, with an interesting observation!

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

The first task to run (which appears to be task#2) succeeds in holding the mutex continuously for 500ms worth of sampling (RTOS tick is 1ms btw).  Since the take/give is so tight, apparently FreeRTOS isn't bothering to let the other task run.  So an xSemaphoreGive() does not automatically invoke a context switch.

 

So we're going to make it voluntarily give up control with taskYIELD() ...

void Task_SPI_Transfer(void *pvParameters) {
    spiTaskParams *params = (spiTaskParams *)pvParameters;

    const char *str = params->str;
    const size_t slen = strlen(str);
    size_t i = 0;
    xSemaphoreHandle spiMutex = params->spiMutex;

    while (1) {
        xSemaphoreTake(spiMutex, portMAX_DELAY); // Suspend this task indefinitely until the mutex is available
        { // braces not necessary but makes the code between the mutex take/give easy to identify
            // 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;
                }
            }
        }
        xSemaphoreGive(spiMutex);
        taskYIELD();
    }
}

And here's the output:

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

That's odd, the two are stepping on each other pretty quickly.  What gives?

Oh right.... I'm only holding the semaphore until I've successfully stuffed the SSI hardware FIFO buffer, then giving it up.  That's what.  Notably, the writer task seems to change every 13 bytes consistently.

Time for a shift in logic.  We want to keep going until i == slen before giving up the mutex:

void Task_SPI_Transfer(void *pvParameters) {
    spiTaskParams *params = (spiTaskParams *)pvParameters;

    const char *str = params->str;
    const size_t slen = strlen(str);
    size_t i = 0;
    xSemaphoreHandle spiMutex = params->spiMutex;

    while (1) {
        xSemaphoreTake(spiMutex, portMAX_DELAY); // Suspend this task indefinitely until the mutex is available
        {
            // Transmit the whole string before giving up the mutex
            i = 0;
            while (i < slen) {
                // Wait for SPI peripheral to stop transmitting
                while (MAP_SSIBusy(SSI2_BASE)) ;

                while (MAP_SSIDataPutNonBlocking(SSI2_BASE, str[i]) > 0) {
                    i++;
                    if (i >= slen) {
                        break;  // Quit stuffing FIFO once we're done
                    }
                }
            }
        }
        xSemaphoreGive(spiMutex);
        taskYIELD();
    }
}

Now THAT looks correct:

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

Share this post


Link to post
Share on other sites

Let's revisit that taskYIELD thing.  So we know if we use taskYIELD, it guarantees a context switch if there's another runnable thread.  But these two tasks are running at the same priority.  What if they weren't?  Perhaps xSemaphoreGive will perform a context switch if the semaphore makes a higher priority task runnable (theoretically this is known as a "priority inversion" when a lower-priority task can lock a higher priority one, so we will experiment to see that xSemaphoreGive does indeed context switch.)

 

First thing I'll do is add another parameter to the spiTaskParams struct - an unsigned int indicating how many milliseconds to pause between semaphore give and take.  One task will have 0 (no delay), the other (higher priority task) will wait 5ms.

 

main.c code (showing the new parameter inside spiTaskParams and removal of taskYIELD):

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#include <queue.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()

// Data structure featuring a string and mutex for arbitrating SPI I/O.
typedef struct {
    xSemaphoreHandle spiMutex; // this is just a pointer FYI.
    char * str;
    unsigned int delay;
} spiTaskParams;

// Declaring the space for these inside main() causes corruption, so make them global?
spiTaskParams task1_Params, task2_Params;


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) );
    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.


    xSemaphoreHandle spiMutex;
    spiMutex = xSemaphoreCreateMutex();

    task1_Params.spiMutex = spiMutex;
    task1_Params.str = "The quick brown fox";
    task1_Params.delay = 0;
    xTaskCreate(Task_SPI_Transfer, "Task1", 32, (void *)&task1_Params, 4, NULL);

    task2_Params.spiMutex = spiMutex;
    task2_Params.str = "bluehash runs a great forum";
    task2_Params.delay = 5;
    xTaskCreate(Task_SPI_Transfer, "Task2", 32, (void *)&task2_Params, 5, NULL);  // Higher priority

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


void Task_SPI_Transfer(void *pvParameters) {
    spiTaskParams *params = (spiTaskParams *)pvParameters;

    const char *str = params->str;
    const size_t slen = strlen(str);
    size_t i = 0;
    xSemaphoreHandle spiMutex = params->spiMutex;

    while (1) {
        xSemaphoreTake(spiMutex, portMAX_DELAY); // Suspend this task indefinitely until the mutex is available
        {
            // Transmit the whole string before giving up the mutex
            i = 0;
            while (i < slen) {
                // Wait for SPI peripheral to stop transmitting
                while (MAP_SSIBusy(SSI2_BASE)) ;

                while (MAP_SSIDataPutNonBlocking(SSI2_BASE, str[i]) > 0) {
                    i++;
                    if (i >= slen) {
                        break;  // Quit stuffing FIFO once we're done
                    }
                }
            }
        }
        xSemaphoreGive(spiMutex);
        if (params->delay != 0) {
            vTaskDelay(params->delay / portTICK_PERIOD_MS); // Suspend for a parameter-specified delay
        }
    }
}

Yep, as suspected.  When xSemaphoreGive runs, if this makes a higher priority task runnable, it will context switch.

 

Start of program:

b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m 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

Somewhere in the middle (every 5ms, in fact; note up above in main() for task2 we used task2_Params.delay = 5, and task2 runs at priority 5 vs. task1 running at priority 4):

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 b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m 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

You can tell the SSIBusy delays, and the context switches, based on the timing:

post-15991-0-64496700-1459285768_thumb.png

Share this post


Link to post
Share on other sites

@@spirilis

 

Is that the ethernet "connected" board you're using ? Just curious, as I have one of these and had been interested in comms via ethernet . . .

 

EDIT:

That is to say, I've been interested in running an RTOS on it, with some form of ethernet communications. I'd have to look and see whats available to figure out what though.

Share this post


Link to post
Share on other sites

Yeah! Agreed!

 

You've got me seriously thinking of switching away from the CC3200MOD over to the TM4C1294.

 

Please keep teaching!

 

PS: In the spirit of sharing, here is an example freertos project I came across on the e2e forums:

https://github.com/akobyl/TM4C129_FreeRTOS8.2_Demo

Yeah if power/battery/lack of access to ethernet is no concern, the TM4C1294 or E is a beast on resources and peripherals. Just a beast.

 

So next I plan to do gatekeeper SPI, then I want to touch uDMA as I've never done DMA work and want to. With a gatekeeper task.

 

After that networking should be attempted but I dunno which way to go. Since there is already an example for it, maybe FreeRTOS+uIP on TM4C1294 will be easier. I do need to figure out SimpleLinkWiFi for CC3100 for future projects at some point.

Share this post


Link to post
Share on other sites

On another note, I was reading the CC26xx technical reference manual earlier a bit.  The peripherals on that chip are pretty nice too, e.g. variable-bit-length SSI just like Tiva (vs. the CC3200's SPI peripheral which was 8 or 16-bit only IIRC, i.e. it has no support whatsoever for 9-bit SPI LCD displays).  It also has fully muxable GPIO's which can service any digital peripheral's function on any pin (no designated pins for peripheral functions).

Share this post


Link to post
Share on other sites

Moving along...

 

SPI Gatekeeper Task.  To simplify the data structure definitions (important, IMO, because RTOS queues involve sending arbitrary data types of a predetermined length and often that requires creating custom-purpose struct's for different queue usage scenarios) I've extracted the struct's out to datastructs.h:

 

datastructs.h:

/* Data structures used by SPI Gatekeeper tasks et al */

#ifndef DATASTRUCTS_H
#define DATASTRUCTS_H


// Data structure featuring a string and SPI queue handle for arbitrating SPI I/O.
typedef struct {
	xQueueHandle spiQueue;
	char * str;
	unsigned int delay;
} spiTaskParams;


// Data structure which is sent across the SPI gatekeeper task queue
typedef struct {
	xSemaphoreHandle complete; // binary semaphore must be created by client before passing across the queue
	const void * dataout;
	void * datain;
	size_t length;
} spiRequest_t;



#endif /* DATASTRUCTS_H */

The gatekeeper task-oriented implementation will involve tasks sending the GK task a copy of everything it needs to perform a contiguous SPI "request" on its behalf. (FreeRTOS xQueueSendToFront/Back receives a pointer to the data type but it physically makes a copy of it inside the queue, however any pointers inside the queue will remain unmodified pointing to a certain task's memory space)

 

Well, almost everything.  I'm not specifying any sort of SPI Chip Select lines in here.  A real production-worthy SPI gatekeeper task should have provisions for toggling GPIO's for SPI Chip Select. (It'd also be nice to implement SPI Read support, with the NULL-or-not-NULL state of datain vs. dataout being the signal which indicates which mode the gatekeeper task is supposed to operate)

 

Also, this clearly could not function with FreeRTOS+MPU, since tasks have hardware-enforced boundaries on memory space and the GK task needs to be able to view memory belonging to another task.  The "complete" binary mutex in the spiRequest_t could be thought of as a "Client task, don't touch any of your provided buffers until I've given this mutex!" signal.

 

(note- I haven't looked too deep into FreeRTOS+MPU, it might have a provision for sharing memory among tasks.)

 

So my first main.c implementation:

/*
 * main.c
 */

#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#include <queue.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()

#include "datastructs.h"


// Declaring the space for these inside main() causes corruption, so make them global?
spiTaskParams task1_Params, task2_Params;

void Task_SPI_Transfer(void *);
void Tiva_SPI_Gatekeeper(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) );
    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.

    // Gatekeeper task request queue
    xQueueHandle spiGatekeeperRequestQueue;
    spiGatekeeperRequestQueue = xQueueCreate(4, sizeof(spiRequest_t));  // Up to 4 asynchronously pending SPI requests at a time.

    task1_Params.spiQueue = spiGatekeeperRequestQueue;
    task1_Params.str = "The quick brown fox";
    task1_Params.delay = 0;
    xTaskCreate(Task_SPI_Transfer, "Task1", 32, (void *)&task1_Params, 4, NULL);

    task2_Params.spiQueue = spiGatekeeperRequestQueue;
    task2_Params.str = "bluehash runs a great forum";
    task2_Params.delay = 5;
    xTaskCreate(Task_SPI_Transfer, "Task2", 32, (void *)&task2_Params, 5, NULL);  // Higher priority

    // Create SPI gatekeeper task
    xTaskCreate(Tiva_SPI_Gatekeeper, "SPI_GK", 32, (void *)spiGatekeeperRequestQueue, 2, NULL);  // Low priority

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

void Task_SPI_Transfer(void *pvParameters) {
    spiTaskParams *params = (spiTaskParams *)pvParameters;

    const char *str = params->str;
    const size_t slen = strlen(str);
    xQueueHandle spiQ = params->spiQueue;
    spiRequest_t request;

    while (1) {
        vSemaphoreCreateBinary(request.complete);
        xSemaphoreTake(request.complete, 0); // Semaphore is "given" by default, must take once so it'll block
        request.dataout = (const void *)str;
        request.length = slen;
        request.datain = NULL; // Send-only SPI request, so datain = NULL to signal no reading required

        // Submit SPI request to gatekeeper task, blocking if queue full
        xQueueSendToBack(spiQ, &request, portMAX_DELAY);
        // Block waiting for request.complete to signal SPI request has been fulfilled
        xSemaphoreTake(request.complete, portMAX_DELAY);
        /* If we had other work to do while waiting for our SPI request to be serviced, we could instead use:
         * if (xSemaphoreTake(request.complete, 0) != pdPASS) {
         *     // ... do work ...
         * }
         *
         * However, keep in mind the SPI gatekeeper task needs CPU cycles to run too, and of particular note is how
         * its priority is below ours, so we'd be starving it of CPU until we try the xSemaphoreTake with a >0 delay value.
         * One alternative would be to run the SPI GK at a substantially higher priority (say 7 or 8), but have it Interrupt Driven
         * where an SSI hardware interrupt would trigger upon completing the current FIFO of SPI data so the high priority
         * SPI gatekeeper task isn't hogging CPU all the time, as it's spending most of its time waiting on its IRQ synchronization
         * binary semaphore (there'd be a binary semaphore between the gatekeeper task and its Interrupt Service Routine).
         */

        if (params->delay != 0) {
            vTaskDelay(params->delay / portTICK_PERIOD_MS); // Task blocks for specified amount of time
        }
    }
}

void Tiva_SPI_Gatekeeper(void *pvParameters) {
    xQueueHandle spiRequestQueue = (xQueueHandle)pvParameters;
    // Using SSI2 hardcoded for this example, so no need to provide the SSIx_BASE in pvParameters...

    spiRequest_t currentReq;
    size_t i = 0;

    while (1) {
        xQueueReceive(spiRequestQueue, &currentReq, portMAX_DELAY);  // Wait for incoming request
        // Service request:

        // Note something we never did implement in previous SPI examples: READING.  We won't implement it here either.  Exercise for the reader.
        // Any thorough "gatekeeper task" for a comms peripheral does need to implement everything it can do or reasonably expect the user to want, however.
        if (currentReq.dataout != NULL) {
            i = 0;
            while (i < currentReq.length) {
                while (MAP_SSIBusy(SSI2_BASE)) ;
                while (MAP_SSIDataPutNonBlocking(SSI2_BASE, ((const uint8_t *)(currentReq.dataout))[i]) > 0) {
                    i++;
                    if (i >= currentReq.length) {
                        break;
                    }
                }
            }
        }
        // Done; signal binary semaphore currentReq.complete
        xSemaphoreGive(currentReq.complete);
    }
}

As I soon found out, this didn't run.  What's weird is tracing the code with step-into/etc ... it seemed to run, but if I hit the Play button and left it to its own devices, it always ended up in FaultISR().

 

I believe what happened was, FreeRTOS was allocating memory every time I did this:

    while (1) {
        vSemaphoreCreateBinary(request.complete);
        xSemaphoreTake(request.complete, 0); // Semaphore is "given" by default, must take once so it'll block

specifically the vSemaphoreCreateBinary() piece.  Since that happens every time the string-writer (client task) tries to make a request, and it's not exactly "garbage collected" by the RTOS, that will overrun RAM very fast.

 

Since the binary mutex (simple synchronization primitive often used between ISRs and their software handler tasks, think Swi's in TI-RTOS lingo) is only used by each task once during an iteration of its SPI write attempt, it would make sense to allocate it once and reuse it:

void Task_SPI_Transfer(void *pvParameters) {
    spiTaskParams *params = (spiTaskParams *)pvParameters;

    const char *str = params->str;
    const size_t slen = strlen(str);
    xQueueHandle spiQ = params->spiQueue;
    spiRequest_t request;
    // Don't have it generate a new semaphore with every request - FaultISR() eventually triggers, maybe out of memory?
    vSemaphoreCreateBinary(request.complete);
    xSemaphoreTake(request.complete, 0); // Semaphore is "given" by default, must take once so it'll block

    while (1) {
        request.dataout = (const void *)str;
        request.length = slen;
        request.datain = NULL; // Send-only SPI request, so datain = NULL to signal no reading required

That runs just peachy.

 

Indeed the SPI output is done contiguously with no overlaps as I found with my logic analyzer.  Here's the first fraction of a millisecond of traffic:

post-15991-0-87406000-1459612202_thumb.png

 

Note the lag between each SPI request.  That's a combination of FreeRTOS context switch lag and managing the queues/mutex operations.  Notably slower overall than the pure-mutex solution from earlier (don't have a waveform handy but IIRC it was buttoned up much tighter).

 

Something weird is going on that I need to figure out, because while the higher priority task runs at the beginning and the lower priority task runs repetitively afterward, the next run of the high-priority task ("bluehash runs a great forum") results in the lower priority task being perpetually dead:

 

post-15991-0-51660500-1459612369_thumb.png

Share this post


Link to post
Share on other sites

Well, one minor detail I know now:

 

Instead of using:

	vSemaphoreCreateBinary(request.complete);
	xSemaphoreTake(request.complete, 0); // Semaphore is "given" by default, must take once so it'll block

to create a binary semaphore, the new canonical way is:

	request.complete = xSemaphoreCreateBinary();

which creates the semaphore in a non-given state.  I found this out by source code:

/**
 * semphr. h
 * <pre>vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )</pre>
 *
 * This old vSemaphoreCreateBinary() macro is now deprecated in favour of the
 * xSemaphoreCreateBinary() function.  Note that binary semaphores created using
 * the vSemaphoreCreateBinary() macro are created in a state such that the
 * first call to 'take' the semaphore would pass, whereas binary semaphores
 * created using xSemaphoreCreateBinary() are created in a state such that the
 * the semaphore must first be 'given' before it can be 'taken'.


edit: forgot a minor detail first time around, the xSemaphoreCreateBinary returns a sem and takes no arguments.

Share this post


Link to post
Share on other sites

Anyway that didn't help the low-priority-task deadlock.  Didn't think it would.

 

Moving Tiva_SPI_Gatekeeper's priority up to 7 (so it has higher priority than everything) doesn't help.

 

Adding a taskYIELD() after xSemaphoreGive in Tiva_SPI_Gatekeeper doesn't help.  

 

Adding this to Task_SPI_Transfer changes things a bit:

		if (params->delay != 0) {
			taskYIELD();
			vTaskDelay(params->delay / portTICK_PERIOD_MS); // Task blocks for specified amount of time
			taskYIELD();
		}

Note that task #1, The quick brown fox, has delay set to 0 so it doesn't run that... but task #2 does.  Not sure why this happens but look at this:

 

post-15991-0-02302000-1459698623_thumb.png

 

Task #1 gets one SPI transfer in, as we see zooming into that first sliver of activity:

post-15991-0-69695900-1459698709_thumb.png

 

I have no idea what's going on here.  taskYIELD should yield to a task that is eligible to run at either equal or higher priority, but there isn't any in this case (thinking of task 2), so it proceeds to vTaskDelay, which should then make any other runnable task (task #1) run.

 

Actually I do still have taskYIELD after xSemaphoreGive in Tiva_SPI_Gatekeeper.  Removing that... does nothing (same output on the logic analyzer).

 

Oh.... removing all those yields and doing the original thing, looking closer at the barrage of data coming through SPI, I didn't realize much of it was gibberish!

post-15991-0-30643800-1459699124_thumb.png

 

Wow, I've no idea what's happening now.  And without RTOS-native instrumentation, probably no easy way to find out.  So I may need to look into what FreeRTOS has to offer....

Share this post


Link to post
Share on other sites

Well, not "giving up" yet - might as well check the return value of xQueueReceive() in Tiva_SPI_Gatekeeper before using the queue value.  portMAX_DELAY should mean sleep indefinitely, but who knows...

 

Alas, that doesn't seem to do much - data is still coming through as gibberish and no toggles on PF_1 (meaning every return value of xQueueReceive is pdPASS):

post-15991-0-55100700-1459699793_thumb.png

Share this post


Link to post
Share on other sites

Dangit!  It was insufficient memory.

Changed the task stack size from 32 words to 64 words and voila:

post-15991-0-88523400-1459699993_thumb.png

 

Every 5ms does show the "bluehash runs a great forum" with "The quick brown fox" filling space in between.

Now FreeRTOS does have some sort of instrumentation for analyzing stack usage.  Time to roll back my latest change, and look into that...

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

×