Jump to content
spirilis

My time with FreeRTOS on the TM4C

Recommended Posts

Allrighty... got a handle on things here.

 

Indeed it was insufficient memory, and FreeRTOS's options for investigating these matters is decent enough.

 

They mostly reside around a hook function you can define (vApplicationStackOverflowHook()) which is required based on the value of #define configCHECK_FOR_STACK_OVERFLOW in your FreeRTOSConfig.h:

 

================

#define configCHECK_FOR_STACK_OVERFLOW (1 or 2)
 
Executes vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName ) - supplied by the user
 
Method 1 (#define set to 1):
 
A task's entire execution context is saved onto its stack each time it gets swapped out.  It is likely that this will be the time at which stack usage reaches its peak.  When configCHECK_FOR_STACK_OVERFLOW is set to 1, the kernel checks that the stack pointer remains within the valid stack space after the context has been saved.  The stack overflow hook is called if the stack pointer is found to be outside its valid range.
 
Method 1 is quick to execute, but can miss stack overflows that occur between context switches.
 
Method 2 (#define set to 2):
 
When a task is created, its stack is filled with a known pattern.  Method 2 tests the last valid 20* bytes of the task stack space to verify that this pattern has not been overwritten.  The stack overflow hook function is called if any of the 20 bytes have changed from their expected values.
 
Method 2 is not as quick to execute as method 1, but is still relatively fast, as only 20 bytes are tested.  Most likely, it will catch all stack overflows; however, it is possible (but highly improbable) that some overflows will be missed.
 
* - per include/StackMacros.h, it's actually 16 bytes, not 20?
================
 
My stack size assigned to all 3 tasks was initially 32.  As it turns out, the value needs to be 39 at the minimum (for task1/task2, GK task needs less) to satisfy all tasks' needs.  I'll show you how I discovered (please copy this function all you want):
 

volatile signed char *stackovf_ptrs[8];
void vApplicationStackOverflowHook(TaskHandle_t *pxTask, signed char *pcTaskName) {
	int i=0;

	MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_PIN_1); // red LED comes on indicating stack overflow
	for (i=0; i < 8; i++) {
		if (stackovf_ptrs[i] == NULL || stackovf_ptrs[i] == pcTaskName) { // ensure we record a task only once (return; is important)
			stackovf_ptrs[i] = pcTaskName;  // pointer to task name string, which should be easy to identify in the debugger
			return;
		}
	}
}

For quick reference, here's what actually happens during context switch with configCHECK_FOR_STACK_SIZE == 1, from FreeRTOS/include/StackMacros.h:

#if( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH < 0 ) )

	/* Only the current stack state is to be checked. */
	#define taskCHECK_FOR_STACK_OVERFLOW()																\
	{																									\
		/* Is the currently saved stack pointer within the stack limit? */								\
		if( pxCurrentTCB->pxTopOfStack <= pxCurrentTCB->pxStack )										\
		{																								\
			vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName );	\
		}																								\
	}

#endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */

and for configCHECK_FOR_STACK_OVERFLOW == 2:

#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) && ( portSTACK_GROWTH < 0 ) )

	#define taskCHECK_FOR_STACK_OVERFLOW()																\
	{																									\
		const uint32_t * const pulStack = ( uint32_t * ) pxCurrentTCB->pxStack;							\
		const uint32_t ulCheckValue = ( uint32_t ) 0xa5a5a5a5;											\
																										\
		if( ( pulStack[ 0 ] != ulCheckValue ) ||												\
			( pulStack[ 1 ] != ulCheckValue ) ||												\
			( pulStack[ 2 ] != ulCheckValue ) ||												\
			( pulStack[ 3 ] != ulCheckValue ) )												\
		{																								\
			vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName );	\
		}																								\
	}

#endif /* #if( configCHECK_FOR_STACK_OVERFLOW > 1 ) */

Method #2 is a stack coloration technique albeit using a single byte, not a pattern, but that's obviously easier to implement (and probably cheaper on CPU) at context-switch time.  I find it odd that Richard's book (the FreeRTOS maintainer) says it requires 20 bytes yet the code clearly only compares four 32-bit integers... so I assume it only needs 16 bytes.

 

Using method#2, I've set all 3 tasks (task1 The quick brown fox, task2 bluehash runs a great forum, and the Gatekeeper task) to 36 (words), which would be the original value of 32 plus 4 words to account for the required 16-byte (4-word) buffer in the stack coloration:

 

Debugger showing that all three tasks overran their stack:
post-15991-0-40575000-1459725003_thumb.png
 
Incidentally, despite overrunning their stack, 36 words is enough to make the SPI traffic look correct:
post-15991-0-22389300-1459724994_thumb.png
 
Method #2 for stack analysis is rather nice I would say since it lets you detect stack overrun with a margin of error and correctly ascertain between-context-switch limits.
 
Using a value of 37 for all 3 of the tasks:
post-15991-0-03253600-1459725557_thumb.png

 

All three tasks ran within or over 16 bytes of their stack.

 

Value 38:

post-15991-0-92304400-1459725564_thumb.png

 

Value 39:

post-15991-0-92304400-1459725564_thumb.png

 

Jumped ahead, the gatekeeper task quit overrunning at 41:

post-15991-0-76045900-1459725573_thumb.png

 

Finally the main SPI transfer tasks quit at 43:

post-15991-0-69420400-1459725582_thumb.png

 

post-15991-0-60173500-1459725590_thumb.png

 

So technically the gatekeeper task requires (41-4 =) 37 words of stack space, and client transfer tasks require (43-4 =) 39 words.

 

Testing that with method #1, which doesn't color the stack and doesn't require a margin... everything looks good.

 

As a quick tip, when going from configCHECK_FOR_STACK_SIZE 2 to 1, it registered stack violation with sizes 37 and 39 (for GK and transfer tasks).  I forgot to Rebuild Project - thus any time you modify FreeRTOSConfig.h be sure to rebuild the whole project instead of just hitting the build button.  After rebuilding the project it ran cleanly with no red LED (and no entries in stackovf_ptrs).

Share this post


Link to post
Share on other sites

Okay ... next up, time to sit back with my tablet and the Tiva-C datasheet learning about uDMA.  I really don't want to get *too* much further into this without having a handle on uDMA and what it's about.  I understand (or vaguely intuit from past readings) that DMA is generally supposed to be a godsend for O/S processing models.

Share this post


Link to post
Share on other sites

Okay, taking my first crack at uDMA...

 

I do not have the time to properly document everything in this post, I'll follow up later with each and every decision along with what the datasheet and TivaWare Peripheral Driver Library documentation told me.

 

For right now, I'll post main.c and explain why I *believe* I DIDN'T see any traffic on the SPI bus-

/*
 * 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 "inc/tm4c123gh6pm.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 "driverlib/udma.h"
#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_DMA_Gatekeeper_Init(void);
void Tiva_SPI_DMA_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
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); // uDMA controller
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)) ;
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) ;
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA));

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

    // Configure PF_1, PF_2 for visual feedback of DMA processes
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ;
    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2);
    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2, 0);

    // 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", 48, (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", 48, (void *)&task2_Params, 5, NULL);  // Higher priority

    // Create SPI gatekeeper task
    Tiva_SPI_DMA_Gatekeeper_Init();  // Initializes uDMA
    xTaskCreate(Tiva_SPI_DMA_Gatekeeper, "SPI_GK", 64, (void *)spiGatekeeperRequestQueue, 7, NULL);  // High 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;
    // Don't have it generate a new semaphore with every request - FaultISR() eventually triggers, maybe out of memory?
    request.complete = xSemaphoreCreateBinary();

    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

        // 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
        }
    }
}


/* uDMA-enabled Gatekeeper task.  There's a lot of pieces to this... */

// DMA Control Structure table must be aligned on 1024-byte boundaries.
// #define DMA_MAX_SUPPORTED_CHANNELS defined in datastruct.h
volatile uint32_t DMA_Control[4 * DMA_MAX_SUPPORTED_CHANNELS] __attribute__((aligned(1024)));
volatile xSemaphoreHandle ssi2_udma_notify;
const uint32_t udma_ch13_mask = 1 << 13;

void Tiva_SPI_DMA_Gatekeeper_Init(void) {
    // Init DMA
    MAP_uDMAEnable();
    MAP_uDMAControlBaseSet( (void *) &DMA_Control ); // All writes to DMA_Control happen via driverlib calls
    // Using channel #13 (SSI2_Tx)
    MAP_uDMAChannelAttributeEnable( 13, UDMA_ATTR_REQMASK ); // Mask SSI2TX IRQ for uDMA's purposes
    MAP_uDMAChannelControlSet( (13 | UDMA_PRI_SELECT), (UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_8) );

    // Tell SSI to allow DMA
    MAP_SSIDMAEnable(SSI2_BASE, SSI_DMA_TX);

    ssi2_udma_notify = xSemaphoreCreateBinary();

    MAP_IntEnable(INT_UDMAERR_TM4C123);
    MAP_IntEnable(INT_SSI2_TM4C123);

    MAP_IntPrioritySet( INT_UDMAERR_TM4C123, 7 );  // low priority
    MAP_IntPrioritySet( INT_SSI2_TM4C123, 1 );  // high priority, max supported while using FreeRTOS syscalls

}

volatile uint8_t periph_irq_toggle;
void Tiva_SPI_DMA_Gatekeeper_IRQ_Handler(void) { // This must be registered for SSI2 Tx
    BaseType_t xHigherPriorityTaskWoken = false;

    // Visually indicate IRQ (also examinable via logic analyzer for timing)
    periph_irq_toggle ^= GPIO_PIN_2;
    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, periph_irq_toggle);

    // Check uDMA to see if it's a uDMA request completion that triggered this
    if (MAP_uDMAIntStatus() & udma_ch13_mask) {
        MAP_uDMAIntClear(udma_ch13_mask);
        // Signal ssi2_udma_notify if we're done writing
        xSemaphoreGiveFromISR( ssi2_udma_notify, &xHigherPriorityTaskWoken );
    }

    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

volatile uint32_t _last_udma_error;
void Tiva_uDMA_Master_IRQ_Handler(void) {
    _last_udma_error = MAP_uDMAErrorStatusGet();
    if (_last_udma_error) {
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_PIN_1);
        MAP_uDMAErrorStatusClear();
    }
}

volatile bool did_binsem_take_before_udmachanenable_work;

void Tiva_SPI_DMA_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;
    portBASE_TYPE qRT; // xQueueReceive return value

    // SSI2 Tx FIFO ingress
    const volatile void * ssi2_Tx_FIFO_WRITEREG = &SSI2_DR_R; // from tm4c123gh6pm.h

    while (1) {
        qRT = 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 (pdPASS == qRT) {
            if (currentReq.dataout != NULL) {
                // Set up uDMA to begin transfer
                MAP_uDMAChannelTransferSet( 13,                             \
                                            UDMA_MODE_BASIC,                \
                                            (void *) currentReq.dataout,    \
                                            (void *) ssi2_Tx_FIFO_WRITEREG, \
                                            currentReq.length);
                // Clear semaphore in case it was given by the last IRQ firing
                if (pdFAIL != xSemaphoreTake(ssi2_udma_notify, 0)) {
                    did_binsem_take_before_udmachanenable_work = true;
                } else {
                    did_binsem_take_before_udmachanenable_work = false;
                }
                MAP_uDMAChannelEnable(13); // BOOM! Off to the races.
                // Pend on binary semaphore for SSI2Tx IRQ to signal uDMA completion
                xSemaphoreTake(ssi2_udma_notify, portMAX_DELAY);
            }
            // Done; signal binary semaphore currentReq.complete
            xSemaphoreGive(currentReq.complete);
        }
    }
}


// Stack overflow protection
volatile signed char *stackovf_ptrs[8];
void vApplicationStackOverflowHook(TaskHandle_t *pxTask, signed char *pcTaskName) {
    int i=0;

    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_PIN_1);
    for (i=0; i < 8; i++) {
        if (stackovf_ptrs[i] == NULL || stackovf_ptrs[i] == pcTaskName) { // ensure we record a task only once (return; is important)
            stackovf_ptrs[i] = pcTaskName;  // pointer to task name string, which should be easy to identify in the debugger
            return;
        }
    }
}

The problem becomes evident when you look at the debugger window:

post-15991-0-43266700-1459901253_thumb.png

 

The uDMA Control Structure format is four 32-bit entries:

Pointer to Source Address

Pointer to Destination Address

Control word (config for this channel)

Unused

 

The pointer to dest is in the peripheral space, specifically SSI2_DR (the correct write location for SSI2's TX FIFO).  But look at the source location:

 

0x000018CC

 

That's in Flash space.  Guess where uDMA can't touch ... It has no bus access to the Flash memory, only SRAM (and peripherals).  Next up, once I have more time, moving those SPI transfer strings into an in-SRAM memory buffer and trying it again.  This should only require modification of main() (along with a global SRAM buffer) and modifying the pointer provided to the pvParameters->str entry for the Task_SPI_Transfer tasks.

Share this post


Link to post
Share on other sites

Turns out that was only part of what I was missing.

 

Also forgot this piece:

MAP_uDMAChannelAssign( UDMA_CH13_SSI2TX );

Yeah that might be important.  This configures the UDMACHMAPx registers to assign a particular DMA channel to a particular peripheral trigger that it supports.

 

Then we get beautiful, tight, SPI output:

post-15991-0-67515600-1460301153_thumb.png

 

post-15991-0-46332300-1460301161_thumb.png

 

Note the PF_2 waveform at the bottom, i.e. logic analyzer channel #2.  It transitions up or down every time an IRQ occurs.  Notice how infrequently that is.. relative to the sheer amount of data passing over the wire.  That's the value of DMA.

In between those IRQ firings, other tasks may run - the SPI gatekeeper task isn't spinning CPU cycles polling the MAP_SSI_Busy() function.  This is implemented by the SPI Gatekeeper task pending on its own task-ISR binary semaphore (which makes the task sleep).  The real "running thread" for SPI I/O is inside dedicated hardware, separate from the Cortex-M4 CPU and separate from FreeRTOS control, that of the UDMA controller itself.

Share this post


Link to post
Share on other sites

Wow. So many ideas about my clock project pop into my head when I see your spi waveforms.

 

I will be creating arrays of data for my 60 RGB LEDs. They all have to be sent onto the spi bus once per second (for normal operation) or many times per second for special patterns.

 

Can you queue up several spi dma transactions?

How fast have you got the spi bus running here?

How fast can the spi bus go flat out?

 

Thanks for sharing your discoveries with us @@spirilis!

Share this post


Link to post
Share on other sites

Can you queue up several spi dma transactions?

 

At the moment the code is using basic DMA transfer mode, which means the CPU has to wake up to prepare the next DMA transfer.

 

The ARM uDMA also offers ping-pong mode, where it automatically kicks off a second transfer once the first is completed. Meanwhile the CPU wakes up to set the inactive DMA control structure up for the next transfer. This double buffering means the DMA is kept active and doesn't have to wait on the CPU.

 

Finally there's scatter/gather mode, where the DMA module writes its own control structure(s), allowing up to 256 pre-planned transfers. That one's the most fun ;)

 

How fast can the spi bus go flat out?

 

I'm curious about that too. I'm wondering why SPI_CLK has pauses between bytes in the latest zoomed in scope trace. What with the TX FIFO and DMA feeding the SSI I'd have hoped the Tiva would manage continuous output at 8MHz.

Share this post


Link to post
Share on other sites

Yeah very good Q about the punctuated space between bytes. My guess is the peripheral does this.

Elaborating, what I meant was the SSI peripheral may have this as a built-in thing... a quick way to find out may be to test different bitrates.  I have this configured for 8MHz presently but I'll try 500KHz, 1MHz, then 20MHz (I think it can do that).

 

First, at 8MHz (also it appears it can't do a clean perfect 8MHz since it's dividing binary from 80MHz), the width between bytes is 0.24uS (just checked with the saleae)

 

Now, 500KHz:

post-15991-0-72808400-1460379836_thumb.png

 

1MHz:

post-15991-0-14523100-1460379844_thumb.png

 

20MHz:

post-15991-0-02248400-1460379849_thumb.png

 

(forget the "f" frequency for those last 2, the Saleae software is making the assumption that the space between bytes is part of the waveform)

 

The timing between bytes is proportional to the speed, so it's probably a constant # of SPI clock cycles that it uses as a delay.  Unfortunately this does not appear to be configurable.

 

You can do up to 32-bit SPI comms with this peripheral, which should provide a constant 32-bit waveform, but I would imagine each 32-bit word would then be spaced by a similarly speed-proportionate amount of time.

Share this post


Link to post
Share on other sites

Ah, yes, that looks like a quirk of the SSI. The default SSI mode apparently adds a delay of 1.5 cycles between bytes to pulse the SSInFss line off.

 

post-30355-0-59669900-1460410326_thumb.png

 

I think that calling MAP_SSIAdvFrameHoldEnable(SSI2_BASE) during SSI setup might remove that delay.

Share this post


Link to post
Share on other sites

Ah, yes, that looks like a quirk of the SSI. The default SSI mode apparently adds a delay of 1.5 cycles between bytes to pulse the SSInFss line off.

 

attachicon.gifFreescaleSPI.png

 

I think that calling MAP_SSIAdvFrameHoldEnable(SSI2_BASE) during SSI setup might remove that delay.

Huh... SSICR1 doesn't have the bit that SSIAdvFrameHoldEnable sets, at least on the TM4C123GH6PM, and check out driverlib/rom.h:

#if defined(TARGET_IS_TM4C129_RA0) ||                                         \
    defined(TARGET_IS_TM4C129_RA1) ||                                         \
    defined(TARGET_IS_TM4C129_RA2)
#define ROM_SSIAdvFrameHoldEnable                                             \
        ((void (*)(uint32_t ui32Base))ROM_SSITABLE[20])

Sounds like a Snowflake (TM4C129)-only feature.

Share this post


Link to post
Share on other sites

OK, brief rundown of the "analysis" process for getting started with uDMA.

 

A cursory look at the uDMA chapter in the TM4C123GH6PM datasheet:

Micro Direct Memory Access (?DMA)The TM4C123GH6PM microcontroller includes a Direct Memory Access (DMA) controller, knownas micro-DMA (?DMA). The ?DMA controller provides a way to offload data transfer tasks from theCortex

post-15991-0-87590300-1460419178_thumb.png

post-15991-0-51191000-1460419738_thumb.png

Share this post


Link to post
Share on other sites

Hmm.  What next...

 

I said next I'd do networking.  I think in preparation for that I should remake the gatekeeper task into a C++ class that's semi-encapsulated away (for simplicity's sake and to start down that path), then make it into a task utilizing GPIO SPI chip select management, SPI write/read/read & write, and uDMA.

 

After that, the SimpleLink WiFi CC3100 library should be attempted on Tiva.

Share this post


Link to post
Share on other sites

@@spirilis

 

I just wanted to say again that I appreciate this thread of yours. 

 

I've been walking through your work today and I have been enjoying it.

 

It's kind of like jumping into the deep end and learning how to swim only I'm talking about FreeRTOS and the TM4C123 LP.

 

Thanks!!!!

Share this post


Link to post
Share on other sites

Okay, coming about to my first FreeRTOS class imagined as a C++ class.

 

Couple things:

 

1. main.c has to be main.cpp, otherwise stuff just doesn't build right.

2. C functions inside main.c should be declared with extern "C", which I will illustrate soon.  The reason for this is that without it, the compiler will mangle the symbol name a bit so other code modules might not recognize the function name if it's declared via extern.

3. portYIELD_FROM_ISR() should take the xHigherPriorityTaskWoken variable directly, NOT a pointer to it as I did in previous examples!  Oops.  I didn't see this until I started getting "ISO C++ forbids comparison between pointer and variable" type of compiler errors related to that call.

4. Peripheral base addresses are defined in hw_memmap.h as uint32_t's, not pointers.  The TivaWare driverlib calls expect it that way too.  This is critically important when parameterizing it for C++ templates.  If you need to use a pointer to the address inside the C++ class, you just have to compose it with e.g. volatile uint32_t *ssi_FIFO = (volatile uint32_t *)(ssi_base + SSI_O_DR); or something.

5. In Project Properties > Compiler > Misc, remove the "-std=c99" option, as this applies to C only and will bomb the compile on C++.

 

Also, my understanding is that FreeRTOS won't run a C++ method directly as a task and likewise the ARM NVIC interrupt vector table can't have a C++ method registered as an ISR, you need to wrap it in a C function.  C functions inside a .cpp file do understand the concept of objects and will generate compiler code that correctly loads (internally, into the register parameters to the C++ method call) the pointer to the correct object before running.

 

So my model here is to create wrapper C functions that call the instantiated C++ object's methods for ISRs and for the FreeRTOS task function, then the actual "code" is contained within the C++ class and references all the private variables compartmentalized within that class.

Share this post


Link to post
Share on other sites

Anyway, here's the code for now.  I have not uploaded this to my Tiva yet to test.

 

main.cpp:

/*
 * 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_memmap.h"
#include "inc/hw_gpio.h"
#include "inc/hw_ssi.h"
#include "inc/tm4c123gh6pm.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 "driverlib/udma.h"
#include <string.h> // for strlen()
#include <SPIDMA.h>

#define DMA_MAX_SUPPORTED_CHANNELS 16
SPIDMA<SSI2_BASE, 13, 12, UDMA_CH13_SSI2TX, UDMA_CH12_SSI2RX, 4, INT_SSI2, 1> SpiGk;
volatile uint32_t DMA_Control[4 * DMA_MAX_SUPPORTED_CHANNELS] __attribute__((aligned(1024)));

// C Function proto's
extern "C" {
void wrapper_spigk_task(void *);
void client_test_task(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
	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); // Need this to configure PE for GPIO (SPI ChipSelect) usage
	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); // uDMA controller
	while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)) ;
	while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) ;
	while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOE)) ;
	while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA));

	// 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);
	// Configure PE5 as GPIO Output
	MAP_GPIOPinTypeGPIOOutput(GPIO_PORTE_BASE, GPIO_PIN_5);
	MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_5, GPIO_PIN_5);

	// 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, 20000000, 8); // 8MHz SPI, mode 0, 8 bits
	MAP_SSIEnable(SSI2_BASE);  // SPI peripheral is now live.
	MAP_uDMAEnable();
	MAP_uDMAControlBaseSet( (void *)&DMA_Control );
	
	xQueueHandle spiq = SpiGk.begin();
	xTaskCreate(wrapper_spigk_task, "SpiGK", 64, NULL, 7, NULL);

	// Create a client task
	xTaskCreate(client_test_task, "Client1", 64, spiq, 5, NULL);

	// Start FreeRTOS
	vTaskStartScheduler();
	while(1);
	return 0;
}



// C binding so these functions are available to FreeRTOS task creation and ISRs
extern "C" {

void placeholder_ssi2_irq_handler(void) {
	SpiGk.handleSpiIrq();
}

void wrapper_spigk_task(void *pvParameters) {
	SpiGk.runMainTask();
}

/*
 * pasting this here just to remember the fields...
typedef struct {
	xSemaphoreHandle complete;
	const void * dataout;
	void * datain;
	size_t length;
	uint8_t bitsize;
	volatile void * cs_gpio_base;
	uint8_t cs_gpio_pin;
	uint32_t speed;
} spiRequest_t;
 */

char sbuf[128];
void client_test_task(void *pvParameters) {
	xQueueHandle spiq = (xQueueHandle)pvParameters;
	spiRequest_t sreq;
	sreq.complete = xSemaphoreCreateBinary();
	sreq.bitsize = 8;
	sreq.cs_gpio_base = 0;
	sreq.speed = 8000000UL;
	sreq.datain = NULL;
	sreq.dataout = sbuf;
	strcpy(sbuf, "This is only a test.");
	sreq.length = strlen(sbuf);

	while (1) {
		xQueueSendToBack(spiq, &sreq, portMAX_DELAY); // Pend waiting for queue to become not-full
		xSemaphoreTake(sreq.complete, portMAX_DELAY); // Pend waiting for SPI request to become fulfilled
		// RTOS 20ms delay
		vTaskDelay(20 / portTICK_PERIOD_MS);
	}
}

}; /* extern "C" */

SPIDMA.h, which contains the code and class definition (C++ templated classes work best when they are contained within a header file, not a .cpp file):

/*
 * SPIDMA.h - C++ template class for a Tiva-C series SPI driver using uDMA and FreeRTOS
 *
 *  Created on: May 3, 2016
 *      Author: Eric Brundick <spirilis@linux.com>
 */

#ifndef SPIDMA_H_
#define SPIDMA_H_

#include <stdint.h>
#include <stdbool.h>


#include <FreeRTOS.h>
#include <semphr.h>
#include <queue.h>

// Data types used within the SPIDMA library
typedef struct {
	xSemaphoreHandle complete;
	const void * dataout;
	void * datain;
	size_t length;
	uint8_t bitsize;
	uintptr_t cs_gpio_base;
	uint8_t cs_gpio_pin;
	uint32_t speed;
} spiRequest_t;


// Main code
template <
	uintptr_t           spi_base,
	uint32_t            udma_tx_chan,
	uint32_t            udma_rx_chan,
	uint32_t            udma_tx_funcassignment,
	uint32_t            udma_rx_funcassignment,
	uint32_t            queue_len,
	uint32_t            spi_interrupt_index,
	uint8_t             spi_interrupt_prio >
class SPIDMA {
private:
	volatile xQueueHandle req_queue;
	xSemaphoreHandle irq_swi_trigger;
	uint32_t last_speed;

public:
	__noinline
	SPIDMA() {
		last_speed = 0;
	}

	__noinline
	xQueueHandle begin() {
		irq_swi_trigger = xSemaphoreCreateBinary();
		req_queue = xQueueCreate(queue_len, sizeof(spiRequest_t));

		// Init uDMA
		MAP_uDMAChannelAssign( udma_tx_funcassignment );
		MAP_uDMAChannelAssign( udma_rx_funcassignment );
		MAP_uDMAChannelAttributeEnable( udma_tx_chan, 0 ); //
		MAP_uDMAChannelAttributeEnable( udma_rx_chan, 0 ); //

		// Tell SSI to allow DMA
		MAP_SSIDMAEnable(spi_base, SSI_DMA_TX | SSI_DMA_RX);
		MAP_IntEnable(spi_interrupt_index);
		MAP_IntPrioritySet( spi_interrupt_index, spi_interrupt_prio );

		return req_queue;
	}

	__noinline
	void handleSpiIrq() {
		// This should fire when an SPI transfer is complete.
		BaseType_t xHigherPriorityTaskWoken = false;

		uint32_t udma_ch_mask = (1 << udma_tx_chan) | (1 << udma_rx_chan);
		if (MAP_uDMAIntStatus() & udma_ch_mask) {
			MAP_uDMAIntClear(udma_ch_mask);
			// Signal software handler that we're done with transfer
			xSemaphoreGiveFromISR(irq_swi_trigger, &xHigherPriorityTaskWoken);
		}

		portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
	}

	__noinline
	void runMainTask() {
		spiRequest_t currentReq;
		portBASE_TYPE qRT; // xQueueReceive return value

		// SSI Tx/Rx FIFO
		volatile uintptr_t * ssi_FIFO = (volatile uintptr_t *)(spi_base + SSI_O_DR);

		while (1) {
			qRT = xQueueReceive(req_queue, &currentReq, portMAX_DELAY);  // Wait for incoming request
			// Service request:
			if (pdPASS == qRT) {
				// Ascertain which mode - TX, RX, or both
				if (currentReq.length < 1 || currentReq.dataout == NULL) {
					xSemaphoreGive(currentReq.complete); // nothing to do
					continue;
				}

				uint32_t ctrltxsize = UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_8;
				uint32_t ctrlrxsize = UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_8;
				if (currentReq.bitsize > 8) {
					ctrltxsize = UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_NONE | UDMA_ARB_8;
					ctrlrxsize = UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_8;
				}

				// Prepare TX
				MAP_uDMAChannelControlSet( (udma_tx_chan | UDMA_PRI_SELECT), ctrltxsize );
				MAP_uDMAChannelTransferSet(udma_tx_chan, UDMA_MODE_BASIC, (void *)currentReq.dataout, (void *)ssi_FIFO, currentReq.length);

				if (currentReq.datain != NULL) {
					// Also prepare RX
					// This is the only way to do RX (in fact that makes sense, as SPI master requires
					// the master initiate transfers, which necessarily requires TX)
					MAP_uDMAChannelControlSet( (udma_rx_chan | UDMA_PRI_SELECT), ctrlrxsize );
					MAP_uDMAChannelTransferSet(udma_rx_chan, UDMA_MODE_BASIC, (void *)ssi_FIFO, (void *)currentReq.datain, currentReq.length);
					MAP_uDMAChannelEnable(udma_rx_chan);  // Prime RX side
				}

				// Configure SPI peripheral speed
				if (currentReq.speed > 0 || last_speed == 0) {
					MAP_SSIConfigSetExpClk(spi_base, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, currentReq.speed, currentReq.bitsize);
					last_speed = currentReq.speed;
				}

				// Activate GPIO CS
				bool do_gpiocs = false;
				if (currentReq.cs_gpio_base != 0 && currentReq.cs_gpio_pin) {
					do_gpiocs = true;
					MAP_GPIOPinWrite(currentReq.cs_gpio_base, currentReq.cs_gpio_pin, 0);
				}
				MAP_uDMAChannelEnable(udma_tx_chan);  // BOOM! Off to the races.
				// Pend on binary semaphore for SSITx IRQ to signal uDMA completion
				xSemaphoreTake(irq_swi_trigger, portMAX_DELAY);
				// Deactivate GPIO CS
				if (do_gpiocs) {
					MAP_GPIOPinWrite(currentReq.cs_gpio_base, currentReq.cs_gpio_pin, currentReq.cs_gpio_pin);
				}
				// Done; signal binary semaphore currentReq.complete
				xSemaphoreGive(currentReq.complete);
			} /* if (pdPASS == qRT) */
		} /* while(1) */
	} /* runMainTask method */

	// Client-side access will be implemented by a sister class defined below.
};


// Arduino compatibility client class
class SPIClass {
private:
	xQueueHandle req_queue;
	spiRequest_t reqcache;
	uint32_t defaultSpeed;
public:
	__noinline
	SPIClass() {
		defaultSpeed = 4000000;  // Arduino default
	}

	// commandeered the setModule method for defining the SPI gatekeeper queue
	__noinline
	void setModule(xQueueHandle q) {
		req_queue = q;
	}

	__noinline
	void setDataMode(uint8_t dm) {
		// does nothing here
	}

	__noinline
	void setClockDivider(uint32_t clockDiv) {
		// Do something intelligent with the clock rate
	}

	__noinline
	void setSpeed(uint32_t s) {
		defaultSpeed = s;
		reqcache.speed = s;
	}

	__noinline
	void setBitOrder(uint8_t  { } // does nothing here

	__noinline
	void begin() {
		reqcache.bitsize = 8;
		reqcache.cs_gpio_base = 0;
		reqcache.cs_gpio_pin = 0;
		reqcache.length = 1;
		reqcache.speed = defaultSpeed;
		reqcache.complete = xSemaphoreCreateBinary();
	}

	__noinline
	void end() {} // does nothing

	__noinline
	uint8_t transfer(uint8_t inb) {
		uint8_t inbuf, outbuf;

		inbuf = inb;
		reqcache.bitsize = 8;
		reqcache.datain = &outbuf;
		reqcache.dataout = &inbuf;
		reqcache.length = 1;

		// Submit SPI request to queue, blocking if full
		xQueueSendToBack(req_queue, &reqcache, portMAX_DELAY);
		// Wait until SPI request is fully complete
		xSemaphoreTake(reqcache.complete, portMAX_DELAY);

		// Return outbuf (return data)
		return outbuf;
	}

	__noinline
	uint16_t transfer16(uint16_t inw) {
		uint16_t inbuf, outbuf;

		inbuf = inw;
		reqcache.bitsize = 16;
		reqcache.datain = &outbuf;
		reqcache.dataout = &inbuf;
		reqcache.length = 1;

		// Submit SPI request to queue, blocking if full
		xQueueSendToBack(req_queue, &reqcache, portMAX_DELAY);
		// Wait until SPI request is fully complete
		xSemaphoreTake(reqcache.complete, portMAX_DELAY);

		// Return outbuf (return data)
		return outbuf;
	}

	__noinline
	uint16_t transfer9(uint16_t inw) {
		uint16_t inbuf, outbuf;

		inbuf = inw;
		reqcache.bitsize = 9;
		reqcache.datain = &outbuf;
		reqcache.dataout = &inbuf;
		reqcache.length = 1;

		// Submit SPI request to queue, blocking if full
		xQueueSendToBack(req_queue, &reqcache, portMAX_DELAY);
		// Wait until SPI request is fully complete
		xSemaphoreTake(reqcache.complete, portMAX_DELAY);

		// Return outbuf (return data)
		return outbuf;
	}
};



#endif /* SPIDMA_H_ */

tm4c123gh6pm_startup_ccs_gcc.c sections related to the IRQ:

//*****************************************************************************
//
// External declarations for the interrupt handlers used by the application.
//
//*****************************************************************************
// To be added by user
extern void placeholder_ssi2_irq_handler(void);

extern void xPortPendSVHandler(void);
extern void xPortSysTickHandler(void);
extern void vPortSVCHandler(void);

//*****************************************************************************
//
// The vector table.  Note that the proper constructs must be placed on this to
// ensure that it ends up at physical address 0x0000.0000 or at the start of
// the program if located at a start address other than 0.
//
//*****************************************************************************
__attribute__ ((section(".intvecs")))
void (* const g_pfnVectors[])(void) =
{
    (void (*)(void))((uint32_t)pui32Stack + sizeof(pui32Stack)),
                                            // The initial stack pointer
    ResetISR,                               // The reset handler
    NmiSR,                                  // The NMI handler
    FaultISR,                               // The hard fault handler
    IntDefaultHandler,                      // The MPU fault handler
    IntDefaultHandler,                      // The bus fault handler
    IntDefaultHandler,                      // The usage fault handler
    0,                                      // Reserved
    0,                                      // Reserved
    0,                                      // Reserved
    0,                                      // Reserved
    vPortSVCHandler,                      // SVCall handler
    IntDefaultHandler,                      // Debug monitor handler
    0,                                      // Reserved
    xPortPendSVHandler,                      // The PendSV handler
    xPortSysTickHandler,                      // The SysTick handler
    IntDefaultHandler,                      // GPIO Port A
    IntDefaultHandler,                      // GPIO Port B
    IntDefaultHandler,                      // GPIO Port C
    IntDefaultHandler,                      // GPIO Port D
    IntDefaultHandler,                      // GPIO Port E
    IntDefaultHandler,                      // UART0 Rx and Tx
    IntDefaultHandler,                      // UART1 Rx and Tx
    IntDefaultHandler,                      // SSI0 Rx and Tx
    IntDefaultHandler,                      // I2C0 Master and Slave
    IntDefaultHandler,                      // PWM Fault
    IntDefaultHandler,                      // PWM Generator 0
    IntDefaultHandler,                      // PWM Generator 1
    IntDefaultHandler,                      // PWM Generator 2
    IntDefaultHandler,                      // Quadrature Encoder 0
    IntDefaultHandler,                      // ADC Sequence 0
    IntDefaultHandler,                      // ADC Sequence 1
    IntDefaultHandler,                      // ADC Sequence 2
    IntDefaultHandler,                      // ADC Sequence 3
    IntDefaultHandler,                      // Watchdog timer
    IntDefaultHandler,                      // Timer 0 subtimer A
    IntDefaultHandler,                      // Timer 0 subtimer B
    IntDefaultHandler,                      // Timer 1 subtimer A
    IntDefaultHandler,                      // Timer 1 subtimer B
    IntDefaultHandler,                      // Timer 2 subtimer A
    IntDefaultHandler,                      // Timer 2 subtimer B
    IntDefaultHandler,                      // Analog Comparator 0
    IntDefaultHandler,                      // Analog Comparator 1
    IntDefaultHandler,                      // Analog Comparator 2
    IntDefaultHandler,                      // System Control (PLL, OSC, BO)
    IntDefaultHandler,                      // FLASH Control
    IntDefaultHandler,                      // GPIO Port F
    IntDefaultHandler,                      // GPIO Port G
    IntDefaultHandler,                      // GPIO Port H
    IntDefaultHandler,                      // UART2 Rx and Tx
    IntDefaultHandler,                      // SSI1 Rx and Tx
    IntDefaultHandler,                      // Timer 3 subtimer A
    IntDefaultHandler,                      // Timer 3 subtimer B
    IntDefaultHandler,                      // I2C1 Master and Slave
    IntDefaultHandler,                      // Quadrature Encoder 1
    IntDefaultHandler,                      // CAN0
    IntDefaultHandler,                      // CAN1
    0,                                      // Reserved
    0,                                      // Reserved
    IntDefaultHandler,                      // Hibernate
    IntDefaultHandler,                      // USB0
    IntDefaultHandler,                      // PWM Generator 3
    IntDefaultHandler,                      // uDMA Software Transfer
    IntDefaultHandler,                      // uDMA Error
    IntDefaultHandler,                      // ADC1 Sequence 0
    IntDefaultHandler,                      // ADC1 Sequence 1
    IntDefaultHandler,                      // ADC1 Sequence 2
    IntDefaultHandler,                      // ADC1 Sequence 3
    0,                                      // Reserved
    0,                                      // Reserved
    IntDefaultHandler,                      // GPIO Port J
    IntDefaultHandler,                      // GPIO Port K
    IntDefaultHandler,                      // GPIO Port L
    placeholder_ssi2_irq_handler,                      // SSI2 Rx and Tx  **** Modified by User ****

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...