Inter-Thread Communications with TeensyThreads

In this post, we continue looking at the TeensyThreads simple threading library for the https://www.pjrc.com/teensy/teensy31.html series of boards.

Background

We will create a simple thread-safe producer and consumer.  The producer will simulate reading some values (e.g. from a sensor), then the consumer will read these values and print them to the serial port.  The only tricky thing about this type of project is ensuring reliable signalling between the two threads that the measurement is ready, and reading the data (a simple struct in this case) atomically, so there are no race conditions.

We’ll use a std::atomic<bool> variable as the flag to indicate a new sample is ready, and use the Mutex class defined in TeensyThreads to lock the data for reading, hopefully without data races.

Producer Thread

Here is the code for the producer.  It’s pretty simple.

static void producer_thread_func(void* arg) {
    static int c;
    while (true) {
        // wait until the consumer has read the sample and
        // unlocked the mutex
        while (mutex.try_lock() == 0) {
            threads.yield();
        }

        // Assign some dummy sample data
        sample.x = c++;
        sample.y = c / 2;

        // We've finished updating the sample now, so can unlock
        // the mutex.
        mutex.unlock();

        // and indicate the sample is ready for reading.
        sampleReady = true;
        threads.delay(500);
    }
}

Consumer Thread

Here’s the code for the corresponding consumer thread, that reads and displays what the producer thread is producing.

static void consumer_thread_func(void* arg) {
    char buf[64];
    uint32_t x;
    uint32_t y;

    while (true) {
        Serial.println("Waiting for sample to be ready");

        // Wait until the producer has indicated a new sample is ready.
        while (!sampleReady) {
            threads.yield();
        }
        sampleReady = false;

        // Don't read from shared variable until the lock in the producer
        // has been released.
        while (mutex.try_lock() == 0) {
            threads.yield();
        }
        x = sample.x;
        y = sample.y;

        // finished getting data from the sample, so we can unlock
        // the mutex now.
        mutex.unlock();
        snprintf(buf, sizeof buf, "Sample is ready, x = %ld, y = %ld", x, y);
        Serial.println(buf);
    }
}

Sample Output

Once you have PlatformIO installed, the code can be built, downloaded & run with the command

pio run -t upload

A sample run should look like this:

xxxxxyyyyzzzz

Full Source Code

The full set of code is available on GitHub at https://github.com/smachin1000/teensy_interthread_comms_project.  I hope you enjoyed the post.  Let me know if you can spot any improvements that can be made?

Advertisements

PlatformIO & TeensyThreads

Outline

This post will describe how to set up a PlatformIO project using the TeensyThreads threading library.  The code is posted on my GitHub account at pio_test_project.

Setup

First, ensure that you have Python and PlatformIO installed as described in my previous post.  The next step is to do a git clone of the project.

C:\Users\Sean Machin\Dropbox>git clone https://github.com/smachin1000/pio_test_project
Cloning into 'pio_test_project'...
remote: Counting objects: 30, done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 30 (delta 1), reused 27 (delta 0), pack-reused 0
Unpacking objects: 100% (30/30), done.

After cloning, take a look at the single main source file src/main.cpp:

#include "Arduino.h"

#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif

// Pin 3 on my Teensy 3.1 is wired to an additional LED.
#define LED_OUTPUT_2 3

static const int DEFAULT_STACK_SIZE = 128;

// Prototypes for thread body functions
static void t1_thread_func(void* arg);
static void t2_thread_func(void* arg);

void setup() {
    // initialize LED digital pin as an output.
    pinMode(LED_OUTPUT_2, OUTPUT);
    pinMode(LED_BUILTIN, OUTPUT);
    
    const int t1_id = threads.addThread(t1_thread_func, nullptr, DEFAULT_STACK_SIZE);
    if (t1_id == -1) {
        Serial.println("error creating thread t1");
    }

    const int t2_id = threads.addThread(t2_thread_func, nullptr, DEFAULT_STACK_SIZE);
    if (t2_id == -1) {
        Serial.println("error creating thread t2");
    }
}

void loop() {
    // Nothing to do in loop function now
    delay(1);
}

static void t1_thread_func(void* arg) {
    while (true) {
        digitalWrite(LED_BUILTIN, HIGH);
        threads.delay(500);
        digitalWrite(LED_BUILTIN, LOW);
        threads.delay(500);
    }
}

static void t2_thread_func(void* arg) {
    while (true) {
        digitalWrite(LED_OUTPUT_2, HIGH);
        threads.delay(250);
        digitalWrite(LED_OUTPUT_2, LOW);
        threads.delay(250);
    }
}

The above code sets up two threads, one which toggles the main LED every 500ms and the other which toggles a secondary LED every 250ms.  As per my previous post, the code can be compiled and downloaded to the target with the command “pio run -t upload”.

Summary

This post showed how to create a very simple test project with two threads of control, using the TeensyThreads library.  Pls. post any comments you may have using the form below.

Embedded Programming with PlatformIO

Recently I’ve been programming the Teensy 3.1 based boards using a new (to me) development environment called PlatformIO.  Unlike normal embedded projects where it can take a lot of work to set up cross compilers, debuggers & the rest of the tool-chain, PlatformIO is designed to be very easy to set up, and as we’ll see below, it actually is!

Above : A Teensy 3.1 board

Installing PlatformIO

PlatformIO is cross platform and uses Python, so you’ll need to install Python on your system first.  I already had Python 2.7 on my system, so the rest of this article will assume you’re using Python 2.7 too.

Once you have Python and the “pip” script installed (ensure <python_dir>/scripts is on your system path), platformio can be easily installed with:

pip install platformio

If the install is successful, then you should be able to run the command “pio” from the command line, like this:

C:\Users\smachin>pio
Usage: pio [OPTIONS] COMMAND [ARGS]...

Options:
 --version Show the version and exit.
 -f, --force Force to accept any confirmation prompts.
 -c, --caller TEXT Caller ID (service).
 -h, --help Show this message and exit.

Commands:
 account Manage PIO Account
 boards Embedded Board Explorer
 ci Continuous Integration
 debug PIO Unified Debugger
 device Monitor device or list existing
 home PIO Home
 init Initialize PlatformIO project or update existing
 lib Library Manager
 platform Platform Manager
 remote PIO Remote
 run Process project environments
 settings Manage PlatformIO settings
 test Local Unit Testing
 update Update installed platforms, packages and libraries
 upgrade Upgrade PlatformIO to the latest version

Creating a New Project

A new PlatformIO project can be created with the “init” command as shown below:

C:\Users\Sean Machin\Dropbox\pio_test_project>pio init --board teensy31

The current working directory C:\Users\Sean Machin\Dropbox\pio_test_project will be used for project.
You can specify another project directory via
`platformio init -d %PATH_TO_THE_PROJECT_DIR%` command.

The next files/directories have been created in C:\Users\Sean Machin\Dropbox\pio_test_project
platformio.ini - Project Configuration File
src - Put your source files here
lib - Put here project specific (private) libraries

Project has been successfully initialized!
Useful commands:
`platformio run` - process/build project from the current directory
`platformio run --target upload` or `platformio run -t upload` - upload firmware to embedded board
`platformio run --target clean` - clean project (remove compiled files)
`platformio run --help` - additional information

C:\Users\Sean Machin\Dropbox\pio_test_project>dir
 Volume in drive C is Windows
 Volume Serial Number is 3419-8B81

Directory of C:\Users\Sean Machin\Dropbox\pio_test_project

03/20/2018 09:21 AM <DIR> .
03/20/2018 09:21 AM <DIR> ..
03/20/2018 09:21 AM 23 .gitignore
03/20/2018 09:21 AM 1,620 .travis.yml
03/20/2018 09:21 AM <DIR> lib
03/20/2018 09:21 AM 439 platformio.ini
03/20/2018 09:21 AM <DIR> src
 3 File(s) 2,082 bytes
 4 Dir(s) 151,766,966,272 bytes free

C:\Users\Sean Machin\Dropbox\pio_test_project>

The main configuration file is “platformio.ini”, see:

C:\Users\Sean Machin\Dropbox\pio_test_project>more platformio.ini
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html

[env:teensy31]
platform = teensy
board = teensy31
framework = arduino

Next, we’ll add main.cpp under the src directory.  The contents of main.cpp is:

/**
 * Blink
 *
 * Turns on an LED on for one second,
 * then off for one second, repeatedly.
 */
#include "Arduino.h"

#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif

void setup()
{
  // initialize LED digital pin as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  // turn the LED on (HIGH is the voltage level)
  digitalWrite(LED_BUILTIN, HIGH);

  // wait for a second
  delay(1000);

  // turn the LED off by making the voltage LOW
  digitalWrite(LED_BUILTIN, LOW);

   // wait for a second
  delay(1000);
}

The code can be compiled with the “pio run” command:

C:\Users\Sean Machin\Dropbox\pio_test_project>pio run
[03/20/18 09:27:20] Processing teensy31 (platform: teensy; board: teensy31; framework: arduino)
-----------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
PLATFORM: Teensy > Teensy 3.1 / 3.2
SYSTEM: MK20DX256 72MHz 64KB RAM (256KB Flash)
DEBUG: CURRENT(jlink) EXTERNAL(jlink)
Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF MODES: FINDER(chain) COMPATIBILITY(light)
Collected 93 compatible libraries
Scanning dependencies...
No dependencies
Compiling .pioenvs\teensy31\src\main.cpp.o Compiling .pioenvs\teensy31\FrameworkArduino\AudioStream.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\DMAChannel.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\EventResponder.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\HardwareSerial1.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\HardwareSerial2.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\HardwareSerial3.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\HardwareSerial4.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\HardwareSerial5.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\HardwareSerial6.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\IPAddress.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\IntervalTimer.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\Print.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\Stream.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\Tone.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\WMath.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\WString.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\analog.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\avr_emulation.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\eeprom.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\keylayouts.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\main.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\math_helper.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\memcpy-armv7m.S.o
Compiling .pioenvs\teensy31\FrameworkArduino\memset.S.o
Compiling .pioenvs\teensy31\FrameworkArduino\mk20dx128.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\new.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\nonstd.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\pins_teensy.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\ser_print.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial1.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial2.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial3.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial4.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial5.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial6.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\serial6_lpuart.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\touch.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_audio.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_desc.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_dev.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_flightsim.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_inst.cpp.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_joystick.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_keyboard.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_mem.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_midi.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_mouse.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_mtp.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_rawhid.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_seremu.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_serial.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\usb_touch.c.o
Compiling .pioenvs\teensy31\FrameworkArduino\yield.cpp.o
Archiving .pioenvs\teensy31\libFrameworkArduino.a
Indexing .pioenvs\teensy31\libFrameworkArduino.a
Linking .pioenvs\teensy31\firmware.elf
Building .pioenvs\teensy31\firmware.hex
Calculating size .pioenvs\teensy31\firmware.elf
text data bss dec hex filename
6288 1232 2176 9696 25e0 .pioenvs\teensy31\firmware.elf
============================================ [SUCCESS] Took 32.53 seconds ============================================

C:\Users\Sean Machin\Dropbox\pio_test_project>

The program can then be downloaded to the target and executed with “pio run -t upload”:

C:\Users\Sean Machin\Dropbox\pio_test_project>pio run -t upload
[03/20/18 09:30:46] Processing teensy31 (platform: teensy; board: teensy31; framework: arduino)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
PLATFORM: Teensy > Teensy 3.1 / 3.2
SYSTEM: MK20DX256 72MHz 64KB RAM (256KB Flash)
DEBUG: CURRENT(jlink) EXTERNAL(jlink)
Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF MODES: FINDER(chain) COMPATIBILITY(light)
Collected 93 compatible libraries
Scanning dependencies...
No dependencies
Linking .pioenvs\teensy31\firmware.elf
Checking program size
text data bss dec hex filename
6288 1232 2176 9696 25e0 .pioenvs\teensy31\firmware.elf
Building .pioenvs\teensy31\firmware.hex
Configuring upload protocol...
AVAILABLE: jlink, teensy-cli, teensy-gui, teensy-gui
CURRENT: upload_protocol = teensy-gui
Uploading .pioenvs\teensy31\firmware.hex
Opening Teensy Loader...
Rebooting...

======================================================================= [SUCCESS] Took 6.78 seconds =======================================================================

C:\Users\Sean Machin\Dropbox\pio_test_project>

If all goes well, your target board should now be running the program and flashing it’s LED.

Conclusion

In this post we showed how to install the PlatformIO tool-chain and created a sample test project and showed how to download it to the target.

In the next post we’ll look at a simple threading library I’ve been using lately called TeensyThreads.

If you have any comment pls. leave them via the form below:

 

 

 

 

FreeRTOS – Part 2

In this post we’ll look at how to perform inter-task communications.  There are several methods that could be used, but we’ll look at the most advanced one; sending messages via message queues.

Sample Code

The code for this example is stored on my GitHub account : https://github.com/smachin1000/freertos_ipc

FreeRTOS Configuration

The most important settings are shown below. Note that heap use is enabled, as it is needed by queues..

// Enable heap usage for queues
#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 5 * 1024 ) )

#define configMAX_TASK_NAME_LEN        ( 16 )
#define configUSE_TRACE_FACILITY    0
#define configUSE_16_BIT_TICKS        1
#define configIDLE_SHOULD_YIELD        1
#define configUSE_MUTEXES            0

#define configUSE_COUNTING_SEMAPHORES 0

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES         0
#define configMAX_CO_ROUTINE_PRIORITIES ( 0 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */

#define INCLUDE_vTaskPrioritySet        1
#define INCLUDE_uxTaskPriorityGet        1
#define INCLUDE_vTaskDelete                0
#define INCLUDE_vTaskCleanUpResources    1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil            1
#define INCLUDE_vTaskDelay                1

Tasks

In this example there will be two tasks, an analog read task that reads voltages from the on-board potentiometer, and an LED display task, which will read those analog values (via a message queue) and display the value bar graph style on the 8 LEDs on the board.

Queue Definitions

To simplify things, we define a struct that holds a queue “handle” (basically a pointer to a created queue) and a queue length (constant).  We set the queue length to 10 for this example (a fairly arbitrary value chosen that’s >= 1).

typedef struct {
    xQueueHandle queue_h;
    uint16_t QUEUE_LENGTH;
} task_arg_t;

The queue itself is defined in main():

 ta.queue_h = xQueueCreate((unsigned portBASE_TYPE)ta.QUEUE_LENGTH,
                           sizeof(uint16_t));

The call to xQueueCreate specifies the queue length, and the size in bytes of each item in the queue.  We will be sending values from the A/D converter as 16 bit unsigned values, hence each value in the queue is 2 bytes long.

Task Creation

First, the LED task is created, for displaying the values read from the queue:

 c = xTaskCreate( led_task, // task "run" function
                  ( signed portCHAR * ) "led_task", // task name
                  configMINIMAL_STACK_SIZE, // task stack size in 32 bit words (not bytes)
                  &ta, // param to pass to run function
                  tskIDLE_PRIORITY + 1, // task priority
                  NULL ); // task handle

The only thing noteworthy here is that we pass in a pointer to “ta” as the task argument.  ta is the task_arg_t stuct we described earlier.

The second, analog read task is then defined:

 c = xTaskCreate( analog_read_task, // task "run" function
                  ( signed portCHAR * ) "analog_read_task", // task name
                  configMINIMAL_STACK_SIZE, // task stack size in 32 bit words (not bytes)
                  &ta, // param to pass to run function
                  tskIDLE_PRIORITY + 1, // task priority
                  NULL ); // task handle

Note it is also passed in a pointer to ta, so it has a reference to the queue length and queue handle.

Analog Reading & Posting to the Queue

The body of the analog read task is shown below.  Note that it checks the queue is not full before sending.

void analog_read_task(task_arg_t* ta)
{
	const xQueueHandle queue_h = ta->queue_h;

      while (1) {
        const ace_channel_handle_t current_channel = ACE_get_first_channel();
        const uint16_t adc_result = ACE_get_ppe_sample(current_channel);
        const uint16_t value_to_send = median_filter(adc_result);

        if (uxQueueMessagesWaiting(queue_h) < ta->QUEUE_LENGTH) {
            const int xStatus = xQueueSendToBack(queue_h, &value_to_send, 0);
            if (xStatus != pdPASS) {
                /* The send operation could not complete because the queue was full -
                   this must be an error as the queue should never contain more than
                   one item! */
                printf( "Could not send to the queue, error code %d.\r\n", xStatus );
                break;
            }
        }
        else {
            // no space in transmit queue, so don't hog the CPU
            taskYIELD();
        }
    }
}

Also note that the analog value read is passed through a median filter. This is because reading from the potentiometer was quite noisy at it’s lower range.

Reading From The Queue

Queue reading is done by the led_task function shown below:

void led_task(task_arg_t* ta)
{
	const xQueueHandle queue_h = ta->queue_h;

    while (1) {
    	// wait until there's at least one message in the queue
        while (uxQueueMessagesWaiting(queue_h) == 0) {
            taskYIELD();
        }
        // queue should now definitely have at least one value in it, so read the value
        // from the queue
        uint16_t received_value;
        const int xStatus = xQueueReceive(queue_h, &received_value, 0);
        if (xStatus == pdPASS) {
            // value received should be will be 650 to 3800, so threshold and scale it as 0-255 now
            if (received_value < MIN_ANALOG_VALUE) {                 received_value = MIN_ANALOG_VALUE;             }             if (received_value > MAX_ANALOG_VALUE) {
                received_value = MAX_ANALOG_VALUE;
            }
            uint32_t scaled_value = round((received_value - MIN_ANALOG_VALUE) * 255.0 /
                                          (MAX_ANALOG_VALUE - MIN_ANALOG_VALUE));
            // now determine which of the 8 LEDs to light, remembering they
            // are opposite polarity.
            uint8_t v = 0;
            int x;
            for (x = 7;x >= 0;x--) {
                if (scaled_value >= (1 << x)) {
                    v |= (1 << x);
                    scaled_value = scaled_value / 2;
                }
            }
            MSS_GPIO_set_outputs(0xffffffff - v);
        }
        else {
            printf("\r\nError %d reading from queue\r\n", xStatus);
            break;
        }
    }
}

Again, note that it waits for the queue to be non-empty before trying to read a value. Once the 16 bit value is read, it is scaled to display on the 8 LEDs on the board.

Please leave any feedback below:

FreeRTOS – Part 1

This post will take a look at FreeRTOS, the popular open source RTOS.  I will show sample code and projects that run on an Actel Smartfusion 1 Eval. board, using Actel Softconsole 3.2.  Hopefully these examples can be easily adapted to your hardware if needed.  The SmartFusion board is shown below:

smartfusion 1 eval board

Why Look at FreeRTOS?

Linux is great for complex embedded systems requiring high performance networking, filesystems & perhaps a GUI.  But a lot of embedded systems are much simpler requiring reading from a few inputs, performing some algorithims and driving some outputs.  For a system like this you wouldn’t want to drag in all the complexity of the Linux kernel.  Plus the RTOS should be able to give much faster and more deterministic timing (something we’ll look at in detail in a future post).

Example #1 : LED Flashing

This will be a simple example with one task, which will strobe the 8 LEDs on the eval. board “cylon” style.

Configuration

FreeRTOS is very flexible in it’s configuration, features can be included or excluded based on #define symbols in port_config/FreeRTOSConfig.h.  Here is an example snippet below:

#define INCLUDE_vTaskPrioritySet		1
#define INCLUDE_uxTaskPriorityGet		1
#define INCLUDE_vTaskDelete			0
#define INCLUDE_vTaskCleanUpResources	        1
#define INCLUDE_vTaskSuspend			1
#define INCLUDE_vTaskDelayUntil			1
#define INCLUDE_vTaskDelay			1

These defines show that we don’t want to include the vTaskDelete function, but do want to include the other task related functions listed.

Task Creation

Tasks are created with the xTaskCreate function, here’s an example:

    c = xTaskCreate( led_task,                          // task "run" function
                     ( signed portCHAR * ) "led_task",  // task name
                     configMINIMAL_STACK_SIZE,          // task stack size in 32 bit words (not bytes)
                     NULL,                              // params to pass to run function
                     tskIDLE_PRIORITY + 1,              // task priority
                     NULL );                            // task handleTasks are created with the xTask

A call to

vTaskStartScheduler();

then starts the FreeRTOS scheduler.  Here is the entire main.c file that shows the complete task initialization and running: main.c

The LED Flash Task

This task will run forever, and turn on and off the 8 LEDs on a GPIO port in Cylon style (back and forth).  The code is quite short, so here is the contents of the entire file led_task.c:

#include "../drivers/mss_gpio/mss_gpio.h"

#include "FreeRTOS.h"
#include "task.h"

void led_initialization()
{
    /* Configuration of GPIO's */
    MSS_GPIO_config(MSS_GPIO_0 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_1 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_2 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_3 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_4 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_5 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_6 , MSS_GPIO_OUTPUT_MODE );
    MSS_GPIO_config(MSS_GPIO_7 , MSS_GPIO_OUTPUT_MODE );
}

void led_task(void *para)
{
    uint8_t b = 1;
    uint8_t dir = 1;
    while (1) {
    	const uint8_t v = 0xff & ~b;
        MSS_GPIO_set_outputs( v );
        if (dir) {
            b = b << 1;
        }
        else {
            b = b >> 1;
        }
        if (b == 128 || b == 1) {
            dir = !dir;
        }
        vTaskDelay(configTICK_RATE_HZ / 16);
    }
}

The run function called by FreeRTOS is led_task(), which sits in an infinite loop and flashes the LEDs as desired, putting a 1/16 second delay between each LED update.  That’s pretty much all there is to it for creating a single task.  In the next post we’ll create multiple tasks and look at passing some data between them.  The entire source code for this posting is on my GitHub page at freertos_ledflash.  SoftConsole 3.2 for Windows can also be downloaded from here if needed.

One Essential Linux Kernel Setting

When building a new kernel, be sure to enable CONFIG_IKCONFIG=y.  This will cause the file /proc/config.gz to be generated on the running system.  This is a gzipped version of the original .config file used to build the kernel.  It can be easily viewed on the running system with “zcat /proc/config.gz”.

This can be very handy for trying to figure out what features were built into a kernel running on a test device, especially one you did not build yourself.

Experimenting with the Thread Protocol

This post will be the first of a short series where I will configure Thread networks using platforms from various vendors.  First up is the Thread release 0.6.0 from Freescale.  This is an older version, but I had access to all the source from the Freescale beta website, and it works, so is worth looking at.  Future posts will explore the latest Thread releases from NXP and SiLabs.

Setup

Hardware

I am using two Freescale Tower boards (twrkw24d512).  It should also be possible to use the USB dongle (usbkw24d512), but I believe that needs a separate JTAG adapter to program it, so the tower boards are easier to use.

Image result for twrkw24d512

Software

First, check out my thread_nxp repository from GitHub : https://github.com/smachin1000/thread_nxp

This should create a top-level thread_nxp directory, and an “FSL_Thread_Stack_0.6.0” subdirectory, which is what we’ll be using for this set of tests.

You will also need a copy of the IAR compiler for ARM.  Eval. versions are available.

Simple Network Setup

Thread network

In this test, we’ll create a Thread end device (white circle in above diagram) and a Thread router (blue circle), which will act as the leader.  We’ll test network connectivity between the two boards and explore the commands available from the serial console CLI.

End Device

Plugging in the Tower board should have created an additional virtual COM port, so connect to that now (115N81) with a serial console (e.g. putty) in preparation for seeing the output from when we run the project.

Open the end device project “C:\thread_nxp\FSL_Thread_Stack_0.6.0\Thread\app\thread\thread_end_device\iar\twrkw24d512\thread_end_device_twrkw24d512.eww” in IAR.

Thread device configuration is specified in the file thread_end_device_config.h for each of these examples.

Go ahead and build the project and download it to the Tower board.  If you have connected the serial console you will see this startup message when the application has started running:

SHELL build: Jan 6 2017
Copyright (c) 2014 Freescale
End Device Application Demo
Press a board switch or enter 'startnwk' to create or join a Thread network!
Note that a "help" command is available.

Before experimenting too much here, let’s get the router node (the leader) running:

Thread Router

First, locate the virtual COM port created by the 2nd Tower board inserted.  Connect a serial console (115N81) to this COM port.

Locate and open the project “C:\thread_nxp\FSL_Thread_Stack_0.6.0\Thread\app\thread\thread_router\iar\twrkw24d512\thread_router_twrkw24d512.eww”.  As with the end node project, Thread defines are in the file thread_end_device_config.h (no adjustments are necessary, but you may want to look).

Compile  and run the Thread Router application, and you should see this appear on the serial terminal:

SHELL build: Jan 21 2017
Copyright (c) 2014 Freescale
Router Application Demo
Press a board switch or enter 'startnwk' to create or join a Thread network!
Starting network...
Attaching to Thread network on channel 26
Cannot find an existing network

Created a new Thread network on channel 26 and PAN ID:0xface
Interface 0: 6LoWPAN
 Mesh local address (ML16): fd00:db8::ff:fe00:0
 Mesh local address (ML64): fd00:db8::204:9f03:1841:0f
Node has taken the Leader role

 

You will notice that the router node has gone ahead and created a new network, and recognized there are no leaders on that network, so has made itself the leader.

Adding the End Node to the Network

We now need to start the network on the end node with the “startnwk” command, you should see output in the terminal as below:

$ startnwk

Starting network...
Attaching to Thread network on channel 26
Attached to network with PAN ID: 0xface
Node started as Polling End Device
Interface 0: 6LoWPAN
 Mesh local address (ML16): fd00:db8::ff:fe00:01

OK, so at this point we have both nodes joined to the same network.  Let’s use the ifconfig command to see what IP addresses have been assigned to each node.

On the router board, run “ifconfig all”.  On my  board, this gives the output below:

$ ifconfig all
THREAD Configuration

Interface 0: 6LoWPAN
 Link local address: fe80::204:9f03:1841:0f
 Link local address: fe80::ff:fe00:0
 Mesh local address (ML16): fd00:db8::ff:fe00:0
 Mesh local address (ML64): fd00:db8::204:9f03:1841:0f

Running the same command on my end node board gives this:

$ ifconfig all
THREAD Configuration

Interface 0: 6LoWPAN
 Link local address: fe80::204:9f0e:7061:1c
 Link local address: fe80::ff:fe00:01
 Mesh local address (ML16): fd00:db8::ff:fe00:01

Why so many addresses?  Let’s try to explain.

Link Local vs. Mesh Local Addresses

Link local addresses are used if you want to connect to a node only 1 hop away.  Mesh local addresses are used if you want to reach a node more than 1 hop away, however mesh local addresses are never routed beyond the Thread network (i.e. can never make it to the outside Internet).  Link local addresses here are like the “192.168.x.x” addresses in IPV4.

Ping Testing

Since our network is super-simple, trying to ping one board from another should work with either the link local or mesh local addresses.  Let’s try it.  On the router node, run “ping6 -c 5 <end node link local addr>.  On my setup, I see these results:

$ ping6 -c 5 fe80::ff:fe00:01
Pinging fe80::ff:fe00:01 with 32 bytes of data:
Request timed out
Reply sequence number not matching
Reply from fe80::ff:fe00:01: bytes=32 time=451ms
Request timed out
Reply sequence number not matching
Reply from fe80::ff:fe00:01: bytes=32 time=496ms
Request timed out

$ Reply from fe80::ff:fe00:01: bytes=32 time=2480ms

OK, we are getting some connectivity, but not the exact results we expect.  I’ll need to spend some time investigating this and will hopefully post the answer in a future post.

Similarly, you should be able to ping the router node from the end node.  This worked on my system, but did have the sequence number problem shown above that needs to be investigated.

Socket Communication

Let’s now try sending data from one board to another using sockets and UDP.  Fortunately the router application has already set up a socket to listen on (see APP_InitSocketServer in router_app.c).  This is setup to receive UDP datagrams on port 1234.  The receive callback handler interprets the data as plain text commands (see APP_SocketClientRxCallback in router_app.c).

With both the router and end device running, and the network joined, run these commands on the end device to create a socket and send the “Temp” command.

$ socket open udp fd00:db8::ff:fe00:0 1234 6
Opening Socket... OK
Socket id is: 0
$ socket send 0 Temp
Command was sent

The leader should receive the Temp command, and display the IP address it came from:

Temp From IPv6 Address: fd00:db8::ff:fe00:01