Part 3 in a series that gets you RTOS-ready for the MSP432 LaunchPad
When you complete this example, you have a basic RTOS setup that integrates USB, ADC and a blinky. You can use Tasks, Semaphores and a UART interrupt. And you'll know how to register Tasks in the TI-RTOS configuration editor.
Part 1 sets up the project, explains how to configure a task and gets the Blinky running. Part 2 adds an ADC Sample Task. Part 3 enables USB without silly loops when waiting for traffic. |
What you need:
- MSP432 LaunchPad
- 1 micro-USB cable
- Code Composer Studio
- TI-RTOS for MSP43X
A few design assumptions:
In our scenario we need each character immediately. We don't wait for any end of line, CR, LF or whatever character. The code can be easily adapted to do that if you want it.
We'll use an interrupt to sense incoming data
The output to USB is straightforward streamed. We assume that all the data is ready in some array when it's time to send it. Again other scenarios can be buid, but we don't do that here.
Wait for Data in RTOS without Processor Overhead
It's a standard functionality of the framework. In TI-RTOS, you can make a task inactive until something happens.
That can be the scheduler waking up, or - in our case - a message being sent from an interrupt when data arrives on the USB port.
This solution takes care that your firmware can spend its focus on actual work while there's no data arriving at the port. With liitle effort from your side.
You'll need
- an RTOS task that initialises the USB peripheral and waits for data.
- an interrupt handler that gives that task a kick when there's a character appearing on the USB bus receive line.
The USB Task and Semaphores
Unlike other RTOS tasks, this one doesn't need a schedule. It's not woken by a scheduler but by an message coming from an interrupt.
The technique we're using is a binary semaphore. This is a technique where you can make one thing wait for something else to happen.
The waiting doesn't cost. It's handled in the limited overhead computing use of the TI-RTOS, the same chunk of code that handles scheduling and prioritisation.
In our task, we initialise the USB (UART) driver and tell it that we want to use an interrupt when data arrives.
We also prepare the semaphore (inactivate + wait) mechanism that we'll use later when the program is ready for business.
#include <uart_impl.h> UART_Handle uart; Semaphore_Handle SEM_uart_rx; // this binary semaphore handles uart receiving interrupts void UART00_IRQHandler(UART_Handle handle, void *buffer, size_t num); /* * ======== fnTaskUART ======== * Task for this function is created statically. See the project's .cfg file. */ Void fnTaskUART(UArg arg0, UArg arg1) { char input; UART_Params uartParams; Error_Block eb; Semaphore_Params sem_params; /* Create a UART with data processing off. */ UART_Params_init(&uartParams); uartParams.writeDataMode = UART_DATA_BINARY; uartParams.readDataMode = UART_DATA_BINARY; uartParams.readReturnMode = UART_RETURN_FULL; uartParams.readEcho = UART_ECHO_OFF; uartParams.baudRate = 9600; uartParams.readMode = UART_MODE_CALLBACK; // the uart uses a read interrupt uartParams.readCallback = &UART00_IRQHandler; // function called when the uart interrupt fires uart = UART_open(Board_UART0, &uartParams); if (uart == NULL) { System_abort("Error opening the UART"); } Semaphore_Params_init(&sem_params); sem_params.mode = Semaphore_Mode_BINARY; SEM_uart_rx = Semaphore_create(0, &sem_params, &eb);
The part that tells the RTOS that we'll use an interrupt is:
uartParams.readMode = UART_MODE_CALLBACK; // the uart uses a read interrupt uartParams.readCallback = &UART00_IRQHandler; // function called when the uart interrupt fires
Then we'll go into a loop that primes the USB driver so that it's ready for incoming data, then sleeps until data arrives.
When a character arrives, our interrupt handler (see next section) wakes us up. We hand over the character to the firmware function that needs it.
Then we repeat priming the USB driver and back to sleep. Until the battery runs out .
while (1) { UART_read(uart, &input, 1); // prime the uart bus to read the first character, non blocking Semaphore_pend(SEM_uart_rx, BIOS_WAIT_FOREVER); // when a character is received via UART, the interrupt handler will release the binary semaphore // in my case: I get an interrupt for a single character, no need to loop. GPIO_toggle(Board_LED1); // LED B - visual clue that we've received a request over USB ___SEND_CHAR_TO_YOUR_LOGIC((const char *)&input, 1); GPIO_toggle(Board_LED1); // LED B - visual clue off } }
Because we told the USB driver that we're working with callback, the UART_read(1) call will immediately return without data, but it'll tell the driver to call the interrupt handler when 1 character arrives.
(if you don't tell the driver that you're using callbacks, the UART_read() function blocks until there's a character)
The Semaphore_pend() then makes the semaphore block and wait - without burning cpu cycles - until the interrupt releases that semaphore.
When that happens, we toggle a led (yeah, I know), send the character that was sent to the USB port to our code (replace ___SEND_CHAR_TO_YOUR_LOGIC() with your real function that needs the input. Maybe a command parser?)
We toggle that led again ( ) and then reset the port and sleep.
Over and over again.
The USB Input Interrupt Handler
You will have seen a glimps of it twice in the previous code chunks.
We've put a forward definition of it at the start of our oce (because in my case, I've put it in the source file after the first use location in the RTOS task.
Then we see it again in the UART driver initialisation:
void UART00_IRQHandler(UART_Handle handle, void *buffer, size_t num); // ... uartParams.readCallback = &UART00_IRQHandler; // function called when the uart interrupt fires
The code itself:
/* EUSCI A0 UART ISR - The interrupt handler fr UART0 (USB) * this method releases the TI-RTOS binary semaphore SEM_uart_rx * and uartFxn() will process the received c */ void UART00_IRQHandler(UART_Handle handle, void *buffer, size_t num) { Semaphore_post(SEM_uart_rx); }
Very very straightforward. When a character arrives on the USB input, a trigger fires (because we put the driver in callback mode).
The trigger calls UART00_IRQHandler() (because we've told the driver that this is the interrupt service method).
UART00_IRQHandler() then releases the semaphore lock.
That action is enough to wake up that sleeping task and let it consume the character.
Varia
(thank you fmilburn)
Includes needed:
#include <xdc/runtime/Error.h> #include <ti/sysbios/knl/Semaphore.h> #include <ti/drivers/UART.h>
Initialisation of UART in main():
Board_initUART();
A working CCS project is attached to this blog post.
The project echos back each character it receives on the input.
To test, connect to the USB serial port with a terminal program, serial, 9600, 8, 1 N
Check usb_test.c for the code, usb_tes.cfg for the task instantiation.
If you look at this code and understand it, you've learned a few more advanced RTOS principles: Semaphores and interrupt handling.
Additional bonus: when you don't have any other tasks to run during the wait, RTOS will put the controller in a lo power mode. WIN-WIN.
Top Comments