Jump to content

My time with FreeRTOS on the TM4C

Recommended Posts

  • Replies 51
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

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

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&

OK, brief rundown of the "analysis" process for getting started with uDMA.   A cursory look at the uDMA chapter in the TM4C123GH6PM datasheet: Micro Direct Memory Access (?DMA)The TM4C123GH6PM micr

Posted Images

Okay, whatever this problem is, it's an odd one.  The SSI2 IRQ isn't firing.  From what I can gather, the uDMA is doing its thang seeing as the control structure's source address appears to be advanced to the end by the time I pause the debugger and look, but the SSI2 FIFO is empty.  I have the SSI2 peripheral configured to send TX info to the uDMA so I don't know what's wrong.  The uDMA CHIS register is still 0 so maybe that's why the IRQ didn't fire.  But my logic analyzer isn't seeing any change in levels either.


So I'm stumped.  I'm sure it's a software issue since my previous example (pre-C++) worked, but, reverse engineering what's happened here is taking some patience.

Link to post
Share on other sites

Ahh, but the uDMA control word's XFERMODE was still set to BASIC.  uDMA is supposed to set that to NONE when it's completed.

So it's not completed, but clearly it has advanced?  So maybe the SSI peripheral is frozen... but I'm still not sure what I did wrong there.  More digging

Link to post
Share on other sites



From page 965 of the TM4C123GH6PM manual:

15.4 Initialization and Configuration

To enable and initialize the SSI, the following steps are necessary:

... blah blah ...

For each of the frame formats, the SSI is configured using the following steps:

1. Ensure that the SSE bit in the SSICR1 register is clear before making any configuration changes.

The problem here is I was running MAP_SSIEnable(SSI2_BASE) in main() (which sets SSICR1.SSE), but my gatekeeper task does custom reconfiguration of the SSI peripheral for each request to configure which DMA request lines it cares about (TX but only optionally RX), along with what speed & bitrate will be used.  Removing that and doing:

                MAP_uDMAChannelEnable(udma_tx_chan);  // BOOM! Off to the races.
                // Pend on binary semaphore for SSITx IRQ to signal uDMA completion
                xSemaphoreTake(irq_swi_trigger, portMAX_DELAY);
                // Deactivate GPIO CS
                if (do_gpiocs) {
                    MAP_GPIOPinWrite(currentReq.cs_gpio_base, currentReq.cs_gpio_pin, currentReq.cs_gpio_pin);

inside the gatekeeper task's main loop instead solves it.  Only "SSIEnable" the peripheral when we're ready to roll, then disable it after.  Got a working waveform on a TX-only test so far, will test TX/RX later.


In my previous (non-C++) example, the SPI speed was never modified by the gatekeeper i.e. the client tasks could never "request" a different speed or bitrate so that was configured before the init function's MAP_SSIEnable() call.  That's what changed here.

Link to post
Share on other sites
  • 3 weeks later...
  • 4 months later...

Loved the edX/UT Austin RTOS & Bluetooth course (mostly about RTOS, one token lab on bluetooth by dipping your toes into the TI NPI protocol for configuring a CC2650 running a special network processor firmware from afar, but not terribly deep into bluetooth or BLE itself)


For the most part, RTOS's aren't rocket science (although I guess they are used in a lot of rockets), the main criticism I have is TI's TI-RTOS is a convoluted pain in the ass due to its superstructure being based on that "XDC" stuff which adds layer upon layer upon layer upon layer of crap.  Definitely over-engineered beyond comprehension.  Like anything though, you can live with it if you put enough time in...


An RTOS at a high level involves several key features:


1. Multiple managed stacks which delineate separate threads of execution (since registers are pushed onto the stack using native instructions, and return addresses also get stored on the stack, the stack is the essence of the logical concept of a "thread").  From here on "task" refers to a thread (which has its own stack and a Task Control Block (TCB) entry in the scheduler's linked list).


2. The multiple tasks are switched between in a preemptive manner, not cooperative--cooperative multitasking is possible of course but does not support the "real time" nature of a system, i.e. it does not support guaranteed minimum latency & jitter constraints for specific operations--this preemptive task switching typically involves a timer but can also involve innate operations, e.g. "posting" a semaphore is an event that might make a real-time constrained task eligible for execution thus it should run code that checks whether the scheduler should be executed to preempt the current task which posted the semaphore.  Generally hardware interrupts of some type drive the context switch, that is, the act of "halting" the current thread and finding another thread to execute.


3. OS-managed semaphores, the exact data structure is not necessarily provided by the OS but there must be a "post" and "pend" function where, at some point, an attempt to "pend" on a semaphore puts the task doing the pending into a blocked state.  The edX RTOS course had an "OS_Signal" (post) and "OS_Wait" (pend) function which took a pointer to an int32_t, any time the semaphore value went negative, the current task was blocked (in its Task Control Block, we had a field "blocked" which was a pointer to the exact int32_t semaphore that blocked us).  When OS_Signal was run from another running task, if the semaphore became <=0 after incrementing it, a code routine would check all the Task Control Blocks to see if anyone was pending on it, and if so, unblock it & perform a context switch.  Only 1 would be unblocked for any 1 run of OS_Signal, and it was first-come first serve.  There are probably other considerations that needed to be made here.


4. Priority system for tasks.  Real-time constraints necessarily requires that certain tasks have precedence over others, such that a scheduler can make a simple and deterministic decision to execute a real-time task exactly when it becomes eligible for execution, to the exclusion of lower-priority tasks without any ambiguity.  A common idiom is that a hardware interrupt might either handle some of the real-time execution itself (interrupts are, in a sense, impromptu "tasks" borrowing the current stack temporarily) but usually it just posts a semaphore that the real-time handler task is waiting on; after the interrupt completes the context switch will be forced right away, ensuring the high-priority now-eligible-for-execution real time task is switched to without any intermission by the previous task that was running before the interrupt occurred.


5. A "softer" but typical requirement is that tasks must be able to sleep, that is, become ineligible for execution for some time.  This sleep operation causes immediate preemption (context switch) with a timer in the system maintaining the countdown for each sleeping thread and making it eligible for execution when a sleeping thread's timer reaches 0.  The alternative to such a managed sleep is to busy-wait until the timeslice finishes; this may be satisfactory but it's fundamentally a waste of CPU resources.


Beyond this, an RTOS may provide other facilities as needed e.g. File systems, Network drivers, coherent date & time with timezone interpretation, logging, whatever.  The basic RTOS task concept provides a neat platform for assembling more complex software layers and since the semaphore system is used to synchronize tasks in an ad-hoc manner, a variety of complex communication methods can arise e.g. managed FIFO ring buffers, mailboxes & queues, etc.  It's important to logically separate the "core RTOS" features from the "extras and/or fluff" that any particular RTOS solution offers.  FreeRTOS for the most part is a barebones RTOS without the fluff, so that you may implement the fluff from scratch in a manner appropriate to your specific platform.  Obviously there are pros & cons with that approach.


An interesting idiom we learned in the edX course is the concept of circular DSP signal buffers.  This is a type of ring buffer where the "head" pointer location is only updated by the producer, e.g. the hardware interrupt collecting ADC data or whatever, and the whole buffer is read by any other tasks as needed.  Sort've a dynamic write-once, read-many concept where the buffer is read in its entirety each time it's "consumed"--as would be the case with many DSP algorithms (FIR, IIR filters et al).  DSP-optimized processors typically include hardware supported circular buffers to make the addressing within these extremely simple, otherwise a number of CPU cycles are expended every time the circular buffer is written to which can affect the amount of CPU time available to process the data.


Anyway, lots more to talk about, for now I am diving into CC1310 work to get a personal star network IoT system up and running (now that I have 5 end nodes built + a Raspberry Pi CC1310 hat), but eventually I'd like to tinker with some more FreeRTOS examples for the purpose of this thread.

Link to post
Share on other sites

As an addendum, I suspect the MSP430's interrupt features such as the ability to determine return LPM status (__bic_SR_register_on_exit()) and its small size firmly plants the MSP430 in the realm of that which doesn't need an RTOS and where an RTOS is probably not desirable.  Its small size and specific feature sets make it optimal for simpler systems where "real time constraints" are enforced either by peripheral features or by individual interrupts, and the main execution thread is the lowest-priority "idle" task.  The use of volatile variable flags (semaphores if you will) to synchronize features inside your main execution thread can be used to run background post-processing operations off a while() loop inside your main(), after which an LPM mode is entered to save power.

Link to post
Share on other sites

Join the conversation

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

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

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

×   Your previous content has been restored.   Clear editor

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

  • Create New...