ULWOS2: Multithreading on the MSP430

In our previous article we saw an example of our simple thread scheduler ULWOS2 running multiple threads on a Linux host. Now it is time to demonstrate ULWOS2 on a real embedded system! In this article we show how to use ULWOS2 to enable multithreading on a tiny MSP430 microcontroller with only 2 KiB of flash and 128 bytes of RAM!

Porting ULWOS2

It is quite easy to port ULWOS2 to a new microcontroller or architecture, as long as GCC supports it. All you have to do is to design a 1 ms timer which ULWOS2 needs in order to enable the thread sleep timer!

The MSP430F2013 has no 32-bit timers and no tick timer such as the one found on ARM Cortex MCUs. In fact, our tiny MSP430 target sports only one 16-bit timer with two channels (timer A).  It also includes a watchdog (WDT+) which can be configured to operate as an interval timer with a programmable interval, including a 1.024 ms one.

The WDT+ can also operate using the VLO clock source and remain active in low power mode. Since the VLO operates at around 12 kHz, the minimum interval achievable is 5.33 ms.

Now let’s take a look at how the MSP430 implementation looks like. We have a single systemInit function which initializes all the necessary MSP430 peripherals. This means stopping the watchdog, configuring the clock for 16 MHz operation, configuring the I/O port, setting up the timer to provide our 1 ms time-base and enabling interrupts.

void systemInit(void)
{
	WDTCTL = WDTPW | WDTHOLD;		// stop watchdog timer
	// configure the DCO to operate at 16MHz
	DCOCTL = CALDCO_16MHZ;
	BCSCTL1 = CALBC1_16MHZ;
	// set SMCLK to 8MHz
	BCSCTL2 = DIVS_1;
	// configure P1.0 as output
	P1DIR = 0x01;
	// configure Timer A as a free running timer (1us per increment)
	TACTL = TASSEL_2 | ID_3 | MC_2;
	// configure Timer A channel 0 to compare mode
	CCR0 = 999;
	CCTL0 = CCIE;					// CCR0 interrupt enabled
	__bis_SR_register(GIE);        // enable interrupts
}

We also had to write a small interrupt handler for the timer interrupts. It is responsible for incrementing the time-base variable (milliSeconds) on every interrupt (once per millisecond) and also add 1000 to CCR0, so that the next compare happens 1000 us from the current one.

void __attribute__ ((interrupt(TIMERA0_VECTOR))) Timer_A_Ch0 (void)
{
	CCR0 += 1000;		// Add 1ms offset to CCR0
	milliSeconds++;
}

ULWOS2 also expects a function for reading current milliseconds count (ULWOS2_getMilliseconds):

tULWOS2Timer ULWOS2_getMilliseconds(void)
{
	return milliSeconds;
}

Blinking an LED

The first and ubiquitous example couldn’t be anything else than an LED blinker! In this first example (blink.c file) we use ULWOS2 to implement a single-threaded LED blinker. I know, I know, a single thread? What is the point of having a thread scheduler to handle a single thread? Well, I completely agree with that, but the idea here is just to demonstrate ULWOS2 usage and analyzing its memory footprint. Also, the project configures ULWOS2 to support up to four threads. Even though we are running only one, we can have a pretty good idea of what is it like the memory footprint on a four-thread environment.

Below is the code of the thread that blinks the LED.

void thread1(void)
{
	ULWOS2_THREAD_START();
	while(1)
	{
		P1OUT ^= 0x01;				// toggle P1.0
		ULWOS2_THREAD_SLEEP_MS(250);
	}
}

Our application’s main function is also pretty simple:

void main(void)
{
	systemInit();
	ULWOS2_INIT();
	ULWOS2_THREAD_CREATE(thread1, 10);
	ULWOS2_START_SCHEDULER();	
}

On this project we are using Code Composer Studio. The figure below shows some of the configuration needed for this project. Make sure that the target (ULWOS_TARGET) is set to ULWOS2_TARGET_MSP430. Also make sure that the maximum number of threads (ULWOS2_MAX_THREADS) is set to 4 (you can also set it to 1 if you like).

Code Composer Studio Configuration
Code Composer Studio Configuration

Don’t forget that you also need to set the GCC tool-chain:

CCS tool-chain

If the GCC tool-chain is not present, you are gonna need to install it. Follow this link in order to download it.

Once the code is compiled we can check the generated map file or alternatively check the memory allocation tab, a nice CCS feature which shows a graphical representation of memory allocation on the target device. From the image below we can see that ULWOS2 is using only 642 bytes of code memory and 44 bytes of RAM! Note: that count is not considering milliSeconds counter which adds another 2 bytes of RAM for a total of 46. The whole application is using 792 bytes of flash and 52 bytes of RAM.

I don’t know about you, but these numbers are impressively low considering we are doing multithreading on a MSP430 microcontroller with only 2kb of flash and 128 bytes of RAM!

CCS memory allocation

Setting a higher bar

Ok, maybe you didn’t get very impressed with a single thread right? What about running eight threads? That is right, the next example demonstrates an 8-thread application. We have one thread dedicated as a software PWM (controlling the LED on P1.0) and one controlling the LED brightness. Additionally, we have 6 other threads toggling I/Os at different frequencies on pins P1.1 to P1.6!

Breathing LED with ULWOS2
Breathing LED with ULWOS2!

We can see below the code for the software PWM thread and LED brightness control thread:

// software PWM thread on P1.0
void softPWMthread(void)
{
    static int16_t internalDutyCycle;
    int16_t sleepTime;
    ULWOS2_THREAD_START();
    while(1)
    {
        internalDutyCycle = dutyCycle;
        sleepTime = (internalDutyCycle * period) / 100;
        P1OUT |= 0x01;				// turn LED on
        ULWOS2_THREAD_SLEEP_MS(sleepTime);
        sleepTime = ((100 - internalDutyCycle) * period) / 100;
        P1OUT &= ~0x01;              // turn LED off
        ULWOS2_THREAD_SLEEP_MS(sleepTime);
    }
}

// LED brightness control (breathing)
void breathThread(void)
{
    static bool direction = 0;
    ULWOS2_THREAD_START();
    while(1)
    {
        if (!direction) dutyCycle++; else dutyCycle--;
        if (dutyCycle == 100 || dutyCycle == 0) 
        {
            direction = !direction;
            // wait 250ms before starting another cycle
            if (!direction) ULWOS2_THREAD_SLEEP_MS(250);
        } else ULWOS2_THREAD_SLEEP_MS(10); // update PWM every 10ms
    }
}

The source code for the other 6 threads is pretty much like the blinker thread we’ve seen on the blinker example.

This is our main function:

void main(void)
{
    systemInit();
    ULWOS2_INIT();
    ULWOS2_THREAD_CREATE(softPWMthread, 0);
    ULWOS2_THREAD_CREATE(breathThread, 1);
    ULWOS2_THREAD_CREATE(blink1, 2);
    ULWOS2_THREAD_CREATE(blink2, 2);
    ULWOS2_THREAD_CREATE(blink3, 2);
    ULWOS2_THREAD_CREATE(blink4, 2);
    ULWOS2_THREAD_CREATE(blink5, 2);
    ULWOS2_THREAD_CREATE(blink6, 2);   
    ULWOS2_START_SCHEDULER();	
}

Don’t forget to also set the maximum number of threads to 8. You must also exclude from build the first example (blink.c) and include this one (breathing.c).

CCS setting 8 threads

Once everything is set, build the project and check the resulting memory allocation:

CCS – Memory allocation 8 threads

Yes, that is right, we are running 8 threads on a tiny MSP430F2013 with 2kb of flash and 128 bytes of RAM and we are using 65% of the flash and 89% of the RAM! Now this is what I call Ultra Lightweight Operating System!

Closing

In this article we’ve saw how to port and use ULWOS2 on an MSP430F2013. We’ve saw memory usage in two different scenarios and implemented a breathing LED using software PWM. The examples shown here are just for demonstration of how small and yet powerful ULWOS2 can be. I really hope you have enjoyed it. In coming articles we will see how to use ULWOS2 on an STM32 microcontroller, Photon Particle (based on the STM32), RISC-V, MSP430 using watchdog and low power mode and much more!

As usual, all the files and projects shown here are available on my Github.

Leave a Reply