Jump to content
43oh

spirilis

Members
  • Content Count

    3,399
  • Joined

  • Last visited

  • Days Won

    146

Reputation Activity

  1. Like
    spirilis got a reaction from tripwire in MSP430FR5969 standby current 0.4uA is it possible.   
    How/where are you measuring it?  Could there be parasitic losses from the eZFET section at the top of the launchpad skewing your numbers?  Have the eZFET jumper block shunts all been pulled?
  2. Like
    spirilis got a reaction from gsutton in MSP430G2955 Launchpad Development   
    IIRC, the only thing in Energia's core really "missing" is Timer_B support specifically compatible with this chip (can't recall if I had a patch in this thread for it, but it might be irrelevant since it's not up to date with Energia 17).  The __MSP430_HAS_TBx__ define for the G2xx[45]'s is a bit different from the one the F5529 uses so the analogWrite code isn't there (on F5529 and I think FR5969, you can use analogWrite on Timer_B ports).  But most people won't care...
  3. Like
    spirilis got a reaction from gsutton in MSP430G2955 Launchpad Development   
    The MSP430G2 LP can program a G2955 using the open source mspdebug program. That ships with Energia fyi. TI's CCS software won't let you use the MSP430G2 LP to program it though.
     
    Sent from my Note 10.1 tablet using Tapatalk
  4. Like
    spirilis got a reaction from gsutton in MSP430G2955 Launchpad Development   
    Going to try my hand soon at a "jack of many trades" G2955 LaunchPad.

     

     
    This model gives me a test bed for playing with the QFN version of the chip, which seems to be easier to come by than the TSSOP version.  (Digikey does have some stock of the TSSOP as it turns out, but still lists it as "Non-Stock".)
     
    Board is based on @@RobG 's original breakout with some minor modifications.  J6/J7 jumpers at the bottom enable connecting P3.1/P3.2 to the P4.1/P4.0 I2C pins in the lower left, allowing me to use BoosterPacks following the new pinout for I2C with the hardware USCI_B I2C port.  On the back is a pair of I2C pullup resistors and solder jumper (broken by default) to enable them.  Up top, we can source power from either USB (w/ MCP1725 500mA 3.3V LDO voltage regulator) or from a 2xAA battery pack (mouser part: http://www.mouser.com/Search/ProductDetail.aspx?R=12BH321P-GRvirtualkey56100000virtualkey12BH321P-GR ) through a TI TPS61221 switching Boost Converter producing regulated 3.3V output with very low quiescent current usage.
     
    Tag-Connect is the principle method of programming, with pads on both sides of the PCB (routing that was "fun") in case a boosterpack is in the way on top, or in case the AA battery pack is installed on the backside (thus requiring programming from the top).
     
    AVcc is powered through a resistor & capacitor filter, and I nestled suitable resistor + capacitor footprints attached to USB D-/D+ so I can run some thin wires from them to I/O ports for using Mecrimus-B software USB if I'd like to try it.  Also the XTAL is LF/HF, with footprints for using either (similar to RobG's design).  P2.6/P2.7 is dedicated for the XTAL now so there is no reason not to install it.  I stole P3.6 and P1.0 from its original locations on RobG's board to supply the pads in the upper right boosterpack header for PWM output and GPIO IRQ.
     
    No buttons besides RESET, only LEDs are Vcc and 5V and they have normally-connected solder jumpers to cut if need be.  If the QFN turns out reasonably OK to solder I'm going to use these as workhorse project "LaunchPads" with the AA battery holder for portable uses.  Might look into putting some mounting holes in though, I hadn't done that initially...
     
    Going to do another once-over on the design, maybe order some tomorrow.
  5. Like
    spirilis got a reaction from tripwire in My time with FreeRTOS on the TM4C   
    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:

     
    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:
     

  6. Like
    spirilis got a reaction from abecedarian in My time with FreeRTOS on the TM4C   
    Picking this forum for a blog thread on learning the "ropes" of FreeRTOS.  TM4C123 launchpad is my learning board for now, using CCSv6 under Windows, latest FreeRTOS and the GNU GCC compiler that ships with CCS (Linaro).
     
    From a fresh download of FreeRTOS, I finally have a working example (not relying on "importing" a CCS example and modifying it - you learn more this way).  Here's main.cpp with all the init + task code:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include <timers.h> #include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" void UsrSW1_Monitor(void *); int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; MAP_GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD); if (pdTRUE != xTaskCreate(UsrSW1_Monitor, "Pushbutton_Monitor", 48, NULL, 3, NULL)) { // Crash due to insufficient heap memory? MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2); // Illuminate R+B for purple while(1) ; } //xTaskCreate(NullTask, "Nothing", 48, (void *)1, 4, NULL); vTaskStartScheduler(); // If FreeRTOS ever exits, illuminate orange (R+G) MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_3); while(1) ; return 0; } void BlinkyTask_LEDBlink(void *pvParams) { unsigned int whichLed = (unsigned int)pvParams; uint8_t whichBit = 1 << whichLed; uint8_t currentValue = 0; while (1) { currentValue ^= whichBit; MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue); vTaskDelay(250 / portTICK_RATE_MS); } } void UsrSW1_Monitor(void *pvParams) { uint16_t inputShifter = 0; bool inputLatched = false; const uint32_t USR_SW1 = GPIO_PIN_4; // PF4 uint8_t whichLed = 1; xTaskHandle blinkerTask; // Start blinker task with current whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } while (1) { // Poll USR_SW1 GPIO - active LOW if (MAP_GPIOPinRead(GPIO_PORTF_BASE, USR_SW1)) { inputShifter <<= 1; } else { inputShifter <<= 1; inputShifter |= 1; } // Test if button has been pressed or released if ( !inputLatched && (inputShifter & 0xF0) == 0xF0 ) { // Rotate LED whichLed++; if (whichLed > 3) { whichLed = 1; } // Kill blinker task vTaskDelete(blinkerTask); // Switch off all LEDs MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, 0); // Start new blinker task with new whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } // Latch button press so we don't re-trigger until button has been released inputLatched = true; } if ( inputLatched && (inputShifter & 0xFF) == 0 ) { inputLatched = false; // un-latch USR_SW1 } // Wait 10ms before checking again vTaskDelay(10 / portTICK_RATE_MS); } } Result: Red LED blinks, left pushbutton toggles to blue & green and back to red.  Pressing & holding the pushbutton switches the LED but only once, as the pushbutton task "latches" it.
     
    The technique of debouncing a pushbutton using periodic polling + bit shifts and logic tests was something I saw on hackaday recently - http://hackaday.com/2015/12/10/embed-with-elliot-debounce-your-noisy-buttons-part-ii/
    I didn't use the same test values but it doesn't matter...
     
    Using polling instead of interrupt for the pushbutton is definitely one of those things that is easy & practical only if using an RTOS.
  7. Like
    spirilis reacted to monsonite in The BSL Bootloader - A Poorman's Guide   
    I got interested in the BSL bootloader about 6 weeks ago whilst trying to find a programming solution for a product at my work.
     
    I have done a bit of research and written it up as a 2 part post on my blog:
     
    Part 1      http://sustburbia.blogspot.co.uk/2016/02/the-great-msp430-bootloader-swindle.html
     
    Part 2      http://sustburbia.blogspot.co.uk/2016/03/the-great-msp430-bootloader-swindle.html
     
     
    If you want to program an MSP430 but don't want to have to use a FET based device, then this could be what you are looking for.
     
    My programmer?  Sure - that's it on the left hand end of my ChipStick board - costs about $1.
     

     
     
     
    Ken
  8. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    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.
  9. Like
    spirilis got a reaction from tripwire in Reply comment window resize glitch   
    This glitch only seems to happen when resizing the quick reply comment window at the bottom of a thread...
     
    Confirmed using Chrome (49.0.2623.110 m) on Windows 10.
     

     
    Notice how the page "jump-scrolls" the moment I click & try to drag the resize handle.  Very glitchy-feeling.
  10. Like
    spirilis got a reaction from yyrkoon in My time with FreeRTOS on the TM4C   
    Picking this forum for a blog thread on learning the "ropes" of FreeRTOS.  TM4C123 launchpad is my learning board for now, using CCSv6 under Windows, latest FreeRTOS and the GNU GCC compiler that ships with CCS (Linaro).
     
    From a fresh download of FreeRTOS, I finally have a working example (not relying on "importing" a CCS example and modifying it - you learn more this way).  Here's main.cpp with all the init + task code:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include <timers.h> #include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" void UsrSW1_Monitor(void *); int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; MAP_GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD); if (pdTRUE != xTaskCreate(UsrSW1_Monitor, "Pushbutton_Monitor", 48, NULL, 3, NULL)) { // Crash due to insufficient heap memory? MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2); // Illuminate R+B for purple while(1) ; } //xTaskCreate(NullTask, "Nothing", 48, (void *)1, 4, NULL); vTaskStartScheduler(); // If FreeRTOS ever exits, illuminate orange (R+G) MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_3); while(1) ; return 0; } void BlinkyTask_LEDBlink(void *pvParams) { unsigned int whichLed = (unsigned int)pvParams; uint8_t whichBit = 1 << whichLed; uint8_t currentValue = 0; while (1) { currentValue ^= whichBit; MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue); vTaskDelay(250 / portTICK_RATE_MS); } } void UsrSW1_Monitor(void *pvParams) { uint16_t inputShifter = 0; bool inputLatched = false; const uint32_t USR_SW1 = GPIO_PIN_4; // PF4 uint8_t whichLed = 1; xTaskHandle blinkerTask; // Start blinker task with current whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } while (1) { // Poll USR_SW1 GPIO - active LOW if (MAP_GPIOPinRead(GPIO_PORTF_BASE, USR_SW1)) { inputShifter <<= 1; } else { inputShifter <<= 1; inputShifter |= 1; } // Test if button has been pressed or released if ( !inputLatched && (inputShifter & 0xF0) == 0xF0 ) { // Rotate LED whichLed++; if (whichLed > 3) { whichLed = 1; } // Kill blinker task vTaskDelete(blinkerTask); // Switch off all LEDs MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, 0); // Start new blinker task with new whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } // Latch button press so we don't re-trigger until button has been released inputLatched = true; } if ( inputLatched && (inputShifter & 0xFF) == 0 ) { inputLatched = false; // un-latch USR_SW1 } // Wait 10ms before checking again vTaskDelay(10 / portTICK_RATE_MS); } } Result: Red LED blinks, left pushbutton toggles to blue & green and back to red.  Pressing & holding the pushbutton switches the LED but only once, as the pushbutton task "latches" it.
     
    The technique of debouncing a pushbutton using periodic polling + bit shifts and logic tests was something I saw on hackaday recently - http://hackaday.com/2015/12/10/embed-with-elliot-debounce-your-noisy-buttons-part-ii/
    I didn't use the same test values but it doesn't matter...
     
    Using polling instead of interrupt for the pushbutton is definitely one of those things that is easy & practical only if using an RTOS.
  11. Like
    spirilis got a reaction from pine in My time with FreeRTOS on the TM4C   
    Picking this forum for a blog thread on learning the "ropes" of FreeRTOS.  TM4C123 launchpad is my learning board for now, using CCSv6 under Windows, latest FreeRTOS and the GNU GCC compiler that ships with CCS (Linaro).
     
    From a fresh download of FreeRTOS, I finally have a working example (not relying on "importing" a CCS example and modifying it - you learn more this way).  Here's main.cpp with all the init + task code:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include <timers.h> #include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" void UsrSW1_Monitor(void *); int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; MAP_GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD); if (pdTRUE != xTaskCreate(UsrSW1_Monitor, "Pushbutton_Monitor", 48, NULL, 3, NULL)) { // Crash due to insufficient heap memory? MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2); // Illuminate R+B for purple while(1) ; } //xTaskCreate(NullTask, "Nothing", 48, (void *)1, 4, NULL); vTaskStartScheduler(); // If FreeRTOS ever exits, illuminate orange (R+G) MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_3); while(1) ; return 0; } void BlinkyTask_LEDBlink(void *pvParams) { unsigned int whichLed = (unsigned int)pvParams; uint8_t whichBit = 1 << whichLed; uint8_t currentValue = 0; while (1) { currentValue ^= whichBit; MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue); vTaskDelay(250 / portTICK_RATE_MS); } } void UsrSW1_Monitor(void *pvParams) { uint16_t inputShifter = 0; bool inputLatched = false; const uint32_t USR_SW1 = GPIO_PIN_4; // PF4 uint8_t whichLed = 1; xTaskHandle blinkerTask; // Start blinker task with current whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } while (1) { // Poll USR_SW1 GPIO - active LOW if (MAP_GPIOPinRead(GPIO_PORTF_BASE, USR_SW1)) { inputShifter <<= 1; } else { inputShifter <<= 1; inputShifter |= 1; } // Test if button has been pressed or released if ( !inputLatched && (inputShifter & 0xF0) == 0xF0 ) { // Rotate LED whichLed++; if (whichLed > 3) { whichLed = 1; } // Kill blinker task vTaskDelete(blinkerTask); // Switch off all LEDs MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, 0); // Start new blinker task with new whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } // Latch button press so we don't re-trigger until button has been released inputLatched = true; } if ( inputLatched && (inputShifter & 0xFF) == 0 ) { inputLatched = false; // un-latch USR_SW1 } // Wait 10ms before checking again vTaskDelay(10 / portTICK_RATE_MS); } } Result: Red LED blinks, left pushbutton toggles to blue & green and back to red.  Pressing & holding the pushbutton switches the LED but only once, as the pushbutton task "latches" it.
     
    The technique of debouncing a pushbutton using periodic polling + bit shifts and logic tests was something I saw on hackaday recently - http://hackaday.com/2015/12/10/embed-with-elliot-debounce-your-noisy-buttons-part-ii/
    I didn't use the same test values but it doesn't matter...
     
    Using polling instead of interrupt for the pushbutton is definitely one of those things that is easy & practical only if using an RTOS.
  12. Like
    spirilis got a reaction from solipso in My time with FreeRTOS on the TM4C   
    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:

  13. Like
    spirilis reacted to dubnet in My time with FreeRTOS on the TM4C   
    @@spirilis
     
    Excellent stuff! I have been intrigued with uC RTOSs and really appreciate the effort it takes to put a tutorial like this together for the community.
  14. Like
    spirilis got a reaction from Fmilburn in My time with FreeRTOS on the TM4C   
    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:

  15. Like
    spirilis got a reaction from dubnet in My time with FreeRTOS on the TM4C   
    Picking this forum for a blog thread on learning the "ropes" of FreeRTOS.  TM4C123 launchpad is my learning board for now, using CCSv6 under Windows, latest FreeRTOS and the GNU GCC compiler that ships with CCS (Linaro).
     
    From a fresh download of FreeRTOS, I finally have a working example (not relying on "importing" a CCS example and modifying it - you learn more this way).  Here's main.cpp with all the init + task code:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include <timers.h> #include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" void UsrSW1_Monitor(void *); int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; MAP_GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD); if (pdTRUE != xTaskCreate(UsrSW1_Monitor, "Pushbutton_Monitor", 48, NULL, 3, NULL)) { // Crash due to insufficient heap memory? MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2); // Illuminate R+B for purple while(1) ; } //xTaskCreate(NullTask, "Nothing", 48, (void *)1, 4, NULL); vTaskStartScheduler(); // If FreeRTOS ever exits, illuminate orange (R+G) MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_3); while(1) ; return 0; } void BlinkyTask_LEDBlink(void *pvParams) { unsigned int whichLed = (unsigned int)pvParams; uint8_t whichBit = 1 << whichLed; uint8_t currentValue = 0; while (1) { currentValue ^= whichBit; MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue); vTaskDelay(250 / portTICK_RATE_MS); } } void UsrSW1_Monitor(void *pvParams) { uint16_t inputShifter = 0; bool inputLatched = false; const uint32_t USR_SW1 = GPIO_PIN_4; // PF4 uint8_t whichLed = 1; xTaskHandle blinkerTask; // Start blinker task with current whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } while (1) { // Poll USR_SW1 GPIO - active LOW if (MAP_GPIOPinRead(GPIO_PORTF_BASE, USR_SW1)) { inputShifter <<= 1; } else { inputShifter <<= 1; inputShifter |= 1; } // Test if button has been pressed or released if ( !inputLatched && (inputShifter & 0xF0) == 0xF0 ) { // Rotate LED whichLed++; if (whichLed > 3) { whichLed = 1; } // Kill blinker task vTaskDelete(blinkerTask); // Switch off all LEDs MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, 0); // Start new blinker task with new whichLed value. if (pdTRUE != xTaskCreate(BlinkyTask_LEDBlink, "Blinker", 32, (void*)whichLed, 4, &blinkerTask)) { // Crash due to insufficient heap memory? Halt scheduler and hold all RGB LEDs to white. vTaskSuspendAll(); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); while(1) ; } // Latch button press so we don't re-trigger until button has been released inputLatched = true; } if ( inputLatched && (inputShifter & 0xFF) == 0 ) { inputLatched = false; // un-latch USR_SW1 } // Wait 10ms before checking again vTaskDelay(10 / portTICK_RATE_MS); } } Result: Red LED blinks, left pushbutton toggles to blue & green and back to red.  Pressing & holding the pushbutton switches the LED but only once, as the pushbutton task "latches" it.
     
    The technique of debouncing a pushbutton using periodic polling + bit shifts and logic tests was something I saw on hackaday recently - http://hackaday.com/2015/12/10/embed-with-elliot-debounce-your-noisy-buttons-part-ii/
    I didn't use the same test values but it doesn't matter...
     
    Using polling instead of interrupt for the pushbutton is definitely one of those things that is easy & practical only if using an RTOS.
  16. Like
    spirilis reacted to zeke in My time with FreeRTOS on the TM4C   
    @@spirilis
     
    Wow!
     
    I can't thank you enough. I ran out of my quota for giving thanks on the forum.
     
    Excellent work!
     
    Please keep going. I'm a willing student. :-)
  17. Like
    spirilis got a reaction from tripwire in My time with FreeRTOS on the TM4C   
    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
  18. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    RTOS concurrency and communications peripherals.  How about we take 2 tasks and have them both spam the SPI peripheral on the Tiva with output?
     
    main.c:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include <stdint.h> #include <stdbool.h> #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" #include "driverlib/ssi.h" // for SPI #include <string.h> // for strlen() void Task_SPI_Transfer(void *); int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2); // SSI2 on PB4/PB6/PB7 MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // Need this to configure PB for SSI usage while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)) ; while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) ; // Configure PB4, PB6, PB7 as SSI pins MAP_GPIOPinTypeSSI(GPIO_PORTB_BASE, (GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7) ); /* Sadly, it's not just enough to configure GPIO pins for the correct peripheral. You also have to set a register called GPIOPCTL for these. * * Register 22: GPIO Port Control (GPIOPCTL), offset 0x52C * The GPIOPCTL register is used in conjunction with the GPIOAFSEL register and selects the specific * peripheral signal for each GPIO pin when using the alternate function mode. Most bits in the * GPIOAFSEL register are cleared on reset, therefore most GPIO pins are configured as GPIOs by * default. When a bit is set in the GPIOAFSEL register, the corresponding GPIO signal is controlled * by an associated peripheral. The GPIOPCTL register selects one out of a set of peripheral functions * for each GPIO, providing additional flexibility in signal definition. */ MAP_GPIOPinConfigure(GPIO_PB4_SSI2CLK); MAP_GPIOPinConfigure(GPIO_PB6_SSI2RX); MAP_GPIOPinConfigure(GPIO_PB7_SSI2TX); // Init SSI2 MAP_SSIClockSourceSet(SSI2_BASE, SSI_CLOCK_SYSTEM); // System CPU clock used MAP_SSIConfigSetExpClk(SSI2_BASE, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 8000000, 8); // 8MHz SPI, mode 0, 8 bits MAP_SSIEnable(SSI2_BASE); // SPI peripheral is now live. // Two EQUAL PRIORITY tasks will continously duke it out trying to send SPI data. // Prototype for xTaskCreate: // BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, // const char * const pcName, // uint16_t usStackDepth, // void *pvParameters, // UBaseType_t uxPriority, // TaskHandle_t *pvCreatedTask); xTaskCreate(Task_SPI_Transfer, "Task1", 32, "The quick brown fox", 4, NULL); xTaskCreate(Task_SPI_Transfer, "Task2", 32, "bluehash runs a great forum", 4, NULL); vTaskStartScheduler(); // Start FreeRTOS! while(1) ; // Shouldn't get here. return 0; } void Task_SPI_Transfer(void *pvParameters) { const char *str = (const char *)pvParameters; const size_t slen = strlen(str); size_t i = 0; while (1) { // Wait for SPI peripheral to stop transmitting while (MAP_SSIBusy(SSI2_BASE)) ; // Stuff the FIFO full while (MAP_SSIDataPutNonBlocking(SSI2_BASE, str[i]) > 0) { i++; // Note: do not be tempted to use "str[i++]" in the while() statement for this. It will increment 'i' even if the call fails. if (i >= slen) { i = 0; } } } } After capturing 100ms of the output, my Saleae Logic16's software did a protocol analysis of the SPI data and after mangling it a bit with GNU awk, here's what it looks like (apostrophes are meant to be spaces):
    b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f o r u m b l u e h a s h ' r u n s ' a ' g r e a t ' f T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n ' f o x T h e ' q u Besides the endless accolades it provides for @@bluehash, of note is that you can tell when the RTOS tick "task switch" occurred:
     
    b l u e h a s h ' r u n s ' a ' g r e a t ' f T h e ' q u i c k ' b r o w n ' f o x T h e ' q u i c k ' b r o w n
     
    Now that's no good!  Even if the point was to spam the SPI bus with data, doing so in coherent blocks/sentences would be preferable.  Those sentences could be coherent blocks of data or control information for an Ethernet peripheral, SPI-attached radio (CC3100, nRF24L01+, etc) or who knows.  Having the data cut in the middle of a logically-contiguous block could cause corruption (especially if separate SPI Chip Select lines are used - but - the task never had a chance to complete its SPI I/O and then deselect the intended peripheral's SPI CS line before the next task goes to town).  Moreover, electrical problems/short circuits could result if multiple SPI slave devices try driving the MISO line due to more than 1 having their Chip Select line selected.  How do we manage this in an RTOS environment?
     
    One way is using Mutual Exclusion - mutex's - and having a common mutex for an SPI peripheral.  Another way, would be a "gatekeeper" task - a task specifically designed to perform SPI communication on everyone else's behalf, using RTOS queues to pass the buffers back and forth.  This gatekeeper task would take a single request off the queue, then run it to completion, returning the results in a private queue encapsulated inside the request for the original caller to digest - all the while, NOT reading from the queue so that other tasks attempting to use the SPI bus will halt.  I am going to try the Mutex idea first, then the gatekeeper task.
  19. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    Wrapping it together, here's our full main.c:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include <stdint.h> #include <stdbool.h> #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" void NullTaskFunc(void *); // prototype so we can include it in main while the code is underneath void BlinkMyLED(void *); int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); // Prototype for xTaskCreate: // // BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, // const char * const pcName, // uint16_t usStackDepth, // void *pvParameters, // UBaseType_t uxPriority, // TaskHandle_t *pvCreatedTask); if (pdTRUE != xTaskCreate(NullTaskFunc, "Null Task", 32, NULL, 4, NULL)) { while (1) ; // Oh no! Must not have had enough memory to create the task. } // For LED blinky task - initialize GPIO port F and then pin #1 (red) for output: MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); // activate internal bus clocking for GPIO port F while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; // busy-wait until GPIOF's bus clock is ready MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1); // PF_1 as output // doesn't need too much drive strength as the RGB LEDs on the TM4C123 launchpad are switched via N-type transistors MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0); // off by default if (pdTRUE != xTaskCreate(BlinkMyLED, "Blinker", 32, (void *)1, 4, NULL)) { // (void *)1 is our pvParameters for our task func specifying PF_1 while (1) ; // error creating task, out of memory? } vTaskStartScheduler(); // Start FreeRTOS! // Should never get here since the RTOS should never "exit". while(1) ; return 0; } // Our RTOS "task" - does absolute jack squat void NullTaskFunc(void *pvParameters) { while (1) { vTaskDelay(10000); // With this task always delaying, the RTOS Idle Task runs almost all the time. } } // The time-honored blinky. void BlinkMyLED(void *pvParameters) { unsigned int whichLed = (unsigned int)pvParameters; /* While pvParameters is technically a pointer, a pointer is nothing * more than an unsigned integer of size equal to the architecture's * memory address bus width, which is 32-bits in ARM. We're abusing * the parameter then to hold a simple integer value. Could also have * used this as a pointer to a memory location holding the value, but * our method uses less memory. */ const uint8_t whichBit = 1 << whichLed; // TivaWare GPIO calls require the pin# as a binary bitmask, not a simple number. // Alternately, we could have passed the bitmask into pvParameters instead of a simple number. uint8_t currentValue = 0; while (1) { currentValue ^= whichBit; // XOR keeps flipping the bit on / off alternately each time this runs. MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue); vTaskDelay(125 / portTICK_RATE_MS); // Suspend this task (so others may run) for 125ms or as close as we can get with the current RTOS tick setting. } // No way to kill this blinky task unless another task has an xTaskHandle reference to it and can use vTaskDelete() to purge it. } Built:

     
    Project running:

     
    Video:

     
     
    Saleae Logic16 sample:

  20. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    Just realized I had my clock off by a factor of 2, it should be SYSCTL_SYSDIV_2_5 not SYSCTL_SYSDIV_5.  Updating the examples above...
     
    And now our blinky example...
     
    Building off main.c above, we'll add some GPIO init inside main():
    // For LED blinky task - initialize GPIO port F and then pin #1 (red) for output: MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); // activate internal bus clocking for GPIO port F while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ; // busy-wait until GPIOF's bus clock is ready MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1); // PF_1 as output // doesn't need too much drive strength as the RGB LEDs on the TM4C123 launchpad are switched via N-type transistors MAP_GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD); MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0); // off by default Then, code for an example task (this is outside of main()):
    // The time-honored blinky. void BlinkMyLED(void *pvParameters) { unsigned int whichLed = (unsigned int)pvParameters; /* While pvParameters is technically a pointer, a pointer is nothing * more than an unsigned integer of size equal to the architecture's * memory address bus width, which is 32-bits in ARM. We're abusing * the parameter then to hold a simple integer value. Could also have * used this as a pointer to a memory location holding the value, but * our method uses less memory. */ const uint8_t whichBit = 1 << whichLed; // TivaWare GPIO calls require the pin# as a binary bitmask, not a simple number. // Alternately, we could have passed the bitmask into pvParameters instead of a simple number. uint8_t currentValue = 0; while (1) { currentValue ^= whichBit; // XOR keeps flipping the bit on / off alternately each time this runs. MAP_GPIOPinWrite(GPIO_PORTF_BASE, whichBit, currentValue); vTaskDelay(125 / portTICK_RATE_MS); // Suspend this task (so others may run) for 125ms or as close as we can get with the current RTOS tick setting. } // No way to kill this blinky task unless another task has an xTaskHandle reference to it and can use vTaskDelete() to purge it. } Finally, back inside main(), we'll create our LED blinker task, pointing it to LED #1 (the task is already hardcoded to use GPIO_PORTF_BASE so that's not changeable but in theory, we could run multiple blinker tasks operating on different LEDs):
    if (pdTRUE != xTaskCreate(BlinkMyLED, "Blinker", 32, (void *)1, 4, NULL)) { // (void *)1 is our pvParameters for our task func specifying PF_1 while (1) ; // error creating task, out of memory? }
  21. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    TivaWare examples usually have a lot of #include's.  This looks like a decent minimal list for doing sysctl configuration (e.g. system clock and enabling peripherals) and GPIO:

     
    The TARGET_IS_TM4C123_RB1 #define we put in the project's symbols enables correct use of the MAP_ and ROM_ version of the various driverlib calls.  Proof is in driverlib/rom.h:
    //***************************************************************************** // // Macros for calling ROM functions in the ADC API. // //***************************************************************************** #if defined(TARGET_IS_TM4C123_RA1) || \ defined(TARGET_IS_TM4C123_RA3) || \ defined(TARGET_IS_TM4C123_RB1) || \ defined(TARGET_IS_TM4C123_RB2) || \ defined(TARGET_IS_TM4C129_RA0) || \ defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) #define ROM_ADCSequenceDataGet \ ((int32_t (*)(uint32_t ui32Base, \ uint32_t ui32SequenceNum, \ uint32_t *pui32Buffer))ROM_ADCTABLE[0]) #endif #if defined(TARGET_IS_TM4C123_RA1) || \ defined(TARGET_IS_TM4C123_RA3) || \ defined(TARGET_IS_TM4C123_RB1) || \ defined(TARGET_IS_TM4C123_RB2) || \ defined(TARGET_IS_TM4C129_RA0) || \ defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) #define ROM_ADCIntDisable \ ((void (*)(uint32_t ui32Base, \ uint32_t ui32SequenceNum))ROM_ADCTABLE[1]) #endif rom_map.h wraps those calls in MAP_ prefixes so that if it's discovered that a certain Tiva silicon release had a bug in its ROM driver code, updated TivaWare editions may choose to use a source-compiled version of the driverlib function instead of the ROM_ version.  We'll use MAP_ for all our driverlib calls.  There is one exception IIRC, I think it's IntRegister(), that can never use the ROM version but we aren't going to touch it during this example.
     
    Note that the exact TARGET define, e.g. TARGET_IS_TM4C123_RB1, should actually be doctored to coincide with the exact silicon you're using.  RB2 is the very latest revision as of March 2016, but if you're building this for an earlier release of the Tiva chip, it might be prudent to investigate the actual CPU revision you're running (see the chip's datasheet - should be gleaned from reading the markings on the chip but you can figure this out programmatically) and use the correct TARGET_IS_TM4C123_Rxy setting.  (I actually think mine is older than RB1, but it's not a big problem for this example).  Anyway, the available versions can be found by examining driverlib/rom.h and then look at the Tiva chip's datasheet + errata sheet for more details on versions, bugs, etc.
     
    Here's our original main.c, reworked to use TivaWare just to properly configure the CPU clock:
    /* * main.c */ #include <FreeRTOS.h> #include <task.h> #include "inc/hw_types.h" #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_gpio.h" #include "driverlib/gpio.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" void NullTaskFunc(void *); // prototype so we can include it in main while the code is underneath int main(void) { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); // Prototype for xTaskCreate: // // BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, // const char * const pcName, // uint16_t usStackDepth, // void *pvParameters, // UBaseType_t uxPriority, // TaskHandle_t *pvCreatedTask); if (pdTRUE != xTaskCreate(NullTaskFunc, "Null Task", 32, NULL, 4, NULL)) { while (1) ; // Oh no! Must not have had enough memory to create the task. } vTaskStartScheduler(); // Start FreeRTOS! // Should never get here since the RTOS should never "exit". while(1) ; return 0; } // Our RTOS "task" - does absolute jack squat void NullTaskFunc(void *pvParameters) { while (1) { vTaskDelay(10000); // With this task always delaying, the RTOS Idle Task runs almost all the time. } } This configures the PLL with correct crystal speed, enabling the crystal oscillator, to land us at 80MHz.
     
    Note that in main.c, if you forgot to include stdbool.h, some of the driverlib .h files will complain:

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

     

     
    Now it builds correctly:

     
    Our FreeRTOS project thus far doesn't do anything visibly different from the last time we built it, besides the CPU clock running at the correct frequency, so we'll skip running it for now.  Next, we're going to create another task that blinks an LED, and I'll include logic analyzer output validating the cadence of the blink.
  22. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    Importing TivaWare-
     
    Right-click on the project, go Import > Import... and then File System.  Browse to your TivaWare install (C:\ti\TivaWare_C_Series-2.1.2.111 in my case):

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

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

     
    At this point you have inc and driverlib:

     
    Now, TivaWare examples typically have you doing #include "inc/tm4c123gh6pm.h" or #include "driverlib/can.h" or whatever.  In order for that to work correctly, our workspace's main directory needs to be in the GNU Compiler's Include path.
     
    Back to our old friend, the Preferences (Alt+Enter or right-click project and hit "Properties"):

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

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

     
    And here's our final include directory list:

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

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

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

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

     

    And voila, we ended up in the middle of one of its internal cleanup functions spawned off the idle task.  At least we're not off in lala land or FaultISR(), instead we're running a validly configured RTOS.  Now this completes the basic FreeRTOS setup, in the next installment (to be continued soon), I'll import TivaWare's driverlib and start doing something useful.  Note that at this stage, FreeRTOS still thinks it's SysTick'ing off an 80MHz clock (due to our current FreeRTOSConfig.h contents) but it's doing no such thing because the CPU is running at the default power-on speed, which is something like 16MHz or 4MHz or whatever.  So all vTaskDelay's are woefully inaccurate right now.  This will be remedied once we use TivaWare driverlib to configure the main clock.
  24. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    To get FreeRTOS into a state where we can compile it, a "FreeRTOSConfig.h" file is required and it looks for it.  We'll create it inside the FreeRTOS/include folder just since we have that folder in our include path already:
     

     
    A reasonable minimum FreeRTOSConfig.h file should include:
    /* Per-Project FreeRTOS Configuration */ /* * Check all the required application specific macros have been defined. * These macros are application specific and (as downloaded) are defined * within FreeRTOSConfig.h. */ #define configMINIMAL_STACK_SIZE 128 // Idle task stack (in 32-bit words) #define configMAX_PRIORITIES 8 // Adjustable but I kept it at 8 for kicks. #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define INCLUDE_vTaskPrioritySet 0 #define INCLUDE_uxTaskPriorityGet 0 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 0 #define INCLUDE_vTaskDelay 1 #define configUSE_16_BIT_TICKS 0 // not sure what this is #define configKERNEL_INTERRUPT_PRIORITY (7 << 5) // Lowest priority for RTOS periodic interrupts #define configMAX_SYSCALL_INTERRUPT_PRIORITY (1 << 5) // Leaves IRQ priority 0 for any non-RTOS Real Time interrupts #define configTOTAL_HEAP_SIZE (8 * 1024) // Adjustable - TM4C123 should support at least 24KB heap #define configCPU_CLOCK_HZ 80000000UL // Full 80MHz clock #define configTICK_RATE_HZ 1000 // 1ms SysTick ticker After writing that out to FreeRTOS/include/FreeRTOSConfig.h, let's try a mock build:

     
    With any luck, it'll build cleanly.  It won't do anything of course since our main() has no references to FreeRTOS but at least the FreeRTOS source files themselves build cleanly with our current configuration.
  25. Like
    spirilis got a reaction from zeke in My time with FreeRTOS on the TM4C   
    With FreeRTOS in our project, we need to make sure the C/C++ compiler is configured to properly search the include and portable folders since FreeRTOS (and our project, soon) depend on it.
     
    First, right-click on the project name and go to Properties (Alt+Enter is the shortcut).  In CCS Build > GNU Compiler, let's have a look at the Directories:

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

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

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

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

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

    "gcc" is required to make TivaWare's SysCtlDelay() compile and TARGET_IS_TM4C123_RB1 enables us access to the ROM version of the various TivaWare driverlib calls.  We will get to TivaWare later, but while we're in here it's good to add the symbols ahead of time.
     
    Under GNU Linker > Basic, we will enable "garbage collection" for unused sections:

     
    This, in combination with the "Place each function" and "Place data items" into their own section parameter, will substantially reduce the size of our compiled firmware to only include that which is truly needed.
     
    The compiler and linker internally produce a tree of symbol links and references, from the ResetISR and main() on down - any functions or data symbols specifically called out or referenced by the code (and referenced by code referenced by that, etc) or linker script will be included in the final code output, and anything not referenced will be "garbage collected" so it doesn't end up in the final firmware binary.
×
×
  • Create New...