Jump to content
43oh

spirilis

Members
  • Content Count

    3,399
  • Joined

  • Last visited

  • Days Won

    146

Posts posted by spirilis

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

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

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

  4. Weird ... any idea what happened?  Was it exposed to the elements?

     

    Fun fact- I showcased a Lightning Sensor project one time at the NY Maker Faire (@@bluehash got 43oh a table that year), and I stored the board stack inside one of the TI booth's cabinets overnight.  Next day it wouldn't work... eventually figured out condensation got to it and the LFXT crystal in particular wasn't working right.  Soldering iron helped temporarily (touching all the pads I could see), but it wasn't until I got home and hit the FR5969 launchpad with a sustained blast from a heat gun that it resolved itself perfectly.

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

  6. Definitely remove all the eZFET jumper blocks, though I've mentioned that already...

     

    Energia does initialize the LFXT, on MSP430G2 chips with no LFXT that has resulted in folks coming onto the forum and complaining about a 2 second startup time (drawing a lot of current in the process) since it'll wait up to 2 seconds for the LFXT to be stable.  It should be using the LFXT too in LPM3 modes (e.g. Energia's own sleep(), sleepSeconds() functions)

     

    edit: If you intend to use the eZFET to power the chip, just connect the GND and 3.3V (Vcc) shunts and nothing else.

  7. No. All factory-provided calibration data for 5xx/6xx flash devices are stored in TLV structure. Info memory (including Info A segment) is (free) not used.

    Double-checked, you are right ...

    #define TLV_START              (0x1A08)       /* Start Address of the TLV structure */
    #define TLV_END                (0x1AFF)       /* End Address of the TLV structure */
     
    infomem lives in 0x1800 - 0x19FF.
    edit: Nevermind the datasheet has a whole section on TLV :)
  8. At the time, when I was working on BSL for 2xx (before I switched forever to SBW), I made automatically updates for password (stored in separate file) with previously builded (not the last one) txt file, to avoid infoA memory segment erase. 

     

     

     

    Same thing is with F2xx family. However, there is msp430g2x44_dco_flashcal.c example in slac548c MSP430G2x44 Code Examples (Rev. C) where (almost) any DCO frequency can be calculated and calibrated values stored to info memory. Example can be found also in code examples for other 2xx devices. 5xx/6xx family use TVL for constants and info memory is not used.

     

    FR5xx/6xx don't use infomem for constants but don't the flash F5xx at least use InfoA?
  9. 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

  10. Office network isn't running DHCP on their network routers or servers?

    That would seem a little odd to me, but, maybe not totally out of the question depending on what kind of environment your office is.  Our datacenters at work don't have DHCP enabled on any of the production networks, but the corporate desktop & VoIP phone networks use DHCP.

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

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

  13. What exactly does "Recalled" mean? I bought two of these when they first came out and have been using them off and on for about a year now. Am I supposed to send them back for exchange or something/ Thanks!

    I think there is a recall exchange available (for the Debug DevPack, not the sensortag itself which is fine).  IIRC, the TI store's online support chat might be able to get you started on that process.

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

  15. 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
    
×
×
  • Create New...