mbed Blog

Simplify your code with mbed-events

In mbed OS 5.2, we introduced mbed-events, an eventing system that can run in an RTOS thread. Using an event loop is very useful to defer execution of code to a different context. An example would be to defer execution from an interrupt context (ISR) to the main loop, or to defer execution from the high-priority thread to a lower priority thread. Now that mbed-events is part of mbed OS 5.2, we'd like to show how this can be used to improve your applications.

For more information about the mbed-events library, have a look at the documentation. All code in this blog post was tested against mbed OS 5.2.3.

Calling printf in an interrupt context

The following program has probably been written by anyone learning how to program microcontrollers. It registers an interrupt handler when a button is pressed, and then calls printf from the ISR.

Naive approach

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(SW2);

void do_something() {
  led = !led;
  printf("Toggle LED!\r\n"); // CRASH! Blocking call in ISR...
}

int main() {
  btn.fall(&do_something);

  while (1) { }
}

When you compile this code with ARMCC, the program will crash right after toggling the LED. This is because calls to stdio (like printf) are guarded by mutexes in the ARM C standard library, and mutex functions cannot be called from an ISR. We can get around this by signalling the main thread from the ISR and do the printf call in there. That's especially confusing when teaching beginners, as now we need to explain the concept of Semaphores or Mailboxes as well.

Using a Semaphore

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(SW2);

Semaphore updates(0);

void do_something() {
  // release the semaphore
  updates.release();
}

int main() {
  btn.fall(&do_something);

  while (1) {
    // wait for the semaphore to be released from the ISR
    int32_t v = updates.wait();

    // now this runs on the main thread, and is safe
    if (v == 1) {
      led = !led;
      printf("Toggle LED!\r\n");
    }
  }
}

While this works, it's a lot more unclear, and we need to build a state machine to determine why the semaphore was released if we're adding more interrupts. Preferably we'd also run this in a separate thread.

With mbed-events we can easily spin up a new RTOS thread with the event loop running in it, and we can defer from ISR to that thread in one line of code.

Using mbed-events

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(SW2);

// create an event queue
EventQueue queue;

void do_something() {
  // this now runs in the context of eventThread, instead of in the ISR
  led = !led;
  printf("Toggle LED!\r\n");
}

int main() {
  // create a thread that'll run the event queue's dispatch function
  Thread eventThread;
  eventThread.start(callback(&queue, &EventQueue::dispatch_forever));

  // wrap calls in queue.event to automatically defer to the queue's thread
  btn.fall(queue.event(&do_something));

  while (1) {}
}

When the interrupt fires, it now automatically defers calling the do_something function to the other thread, from where it's safe to call printf. In addition, we don't need to taint our main thread's main loop with program logic.

Manually deferring from ISR to a thread

The downside of this approach is that both the toggling of the LED and the printf call are now executed outside the ISR and thus are not guaranteed to run straight away. We can work around this by toggling the LED from the ISR, then manually deferring the printf event to the thread.

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(SW2);

EventQueue queue;

void do_something_outside_irq() {
  // this does not run in the ISR
  printf("Toggle LED!\r\n");
}

void do_something_in_irq() {
  // this runs in the ISR
  led = !led;

  // then defer the printf call to the other thread
  queue.call(&do_something_outside_irq);
}

int main() {
  Thread eventThread;
  eventThread.start(callback(&queue, &EventQueue::dispatch_forever));

  btn.fall(&do_something_in_irq);

  while (1) {}
}

Mixing high priority and low priority events

We can differentiate between the importance of events by using multiple threads that run with different priorities. We can easily add a Ticker to the program which toggles LED2 every second, which runs with a higher priority than the printf calls by creating a second event queue.

#include "mbed.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);
InterruptIn btn(SW2);

EventQueue printfQueue;
EventQueue eventQueue;

void blink_led2() {
  // this runs in the normal priority thread
  led2 = !led2;
}

void print_toggle_led() {
  // this runs in the lower priority thread
  printf("Toggle LED!\r\n");
}

void btn_fall_irq() {
  led1 = !led1;

  // defer the printf call to the low priority thread
  printfQueue.call(&print_toggle_led);
}

int main() {
  // low priority thread for calling printf()
  Thread printfThread(osPriorityLow);
  printfThread.start(callback(&printfQueue, &EventQueue::dispatch_forever));

  // normal priority thread for other events
  Thread eventThread(osPriorityNormal);
  eventThread.start(callback(&eventQueue, &EventQueue::dispatch_forever));

  // call blink_led2 every second, automatically defering to the eventThread
  Ticker ledTicker;
  ledTicker.attach(eventQueue.event(&blink_led2), 1.0f);

  // button fall still runs in the ISR
  btn.fall(&btn_fall_irq);

  while (1) {}
}

Conclusion

mbed-events makes it a lot easier to defer calls from one context to another, whether it's from an ISR back to a user thread, or from one thread to another. It also makes it easy to prioritise certain events over other events, and does not require you to write your own state machine or taint your main loop. Since it's a one-liner (wrap the callback in queue.event()) to wrap a call that would normally run in an ISR, it's also very friendly for beginners.

For more information see the documentation.

-

Jan Jongboom is Developer Evangelist IoT at ARM and does not like to explain Semaphores during workshops.

Why JavaScript on microcontrollers makes sense

Three weeks ago, during JSConf.asia 2016, we announced JavaScript on mbed, which enables developers to write firmware for IoT devices in JavaScript. This is not done by transpiling JavaScript into C++ or Assembly, but rather by running the JerryScript VM directly on top of ARM's mbed OS 5, which can be run on cost-effective microcontrollers. This announcement caused an interesting debate, including a heated thread on the Reddit Programming subreddit with 192 comments.

Creative insults on Reddit

And yes, there are valid concerns. JavaScript will be more resource hungry than native code, and it might not be the best idea for every embedded application. But, we also believe that there are some great benefits to running a dynamic, interpreted language on a microcontroller, especially for IoT devices. I'd like to share some of these benefits.

We do want to stress that we’re talking about writing the application layer in a higher level language. The core parts of your embedded OS, including the scheduler, peripheral drivers and network stack will remain native.

Event-driven model

An event-driven model makes a lot of sense for IoT devices, especially on smaller sensor nodes. Many of them only run a networking stack and some peripherals, and respond either to events from the network, some interrupts or the real time clock. Using an event loop for scheduling, rather than manual scheduling, helps with a number of common problems when programming microcontrollers:

  • Networking stacks that want to take over the main loop of the program, making it impossible to mix multiple networking stacks. Here's an example (line 44).
  • Delegation from interrupt context back to main thread to do actual work. Semaphores and mailboxes are complex beasts and taint your code very quickly. Also, the problem might not manifest itself straight away; doing things in ISRs is often fine with GCC but crashes when compiling with ARMCC.

With JavaScript on a microcontroller, not only can we enforce event-driven programming - it's also a model that is already familiar to every JS developer, since that's how JS in the browser works. The higher level APIs that we need to introduce can even completely hide the synchronous calls, similar to how node.js works for server-side code. A main-function or busy-sleep on the CPU is a concept not known in JavaScript, making it a very friendly language to experiment with event-loop constructs on microcontrollers. Additionally, because all JS code will be event loop aware, we can automatically dispatch events from interrupts, not even giving users the opportunity to do blocking calls in an ISR context.

Of course this model is not new, and we can run event loops in C++ code (we have a library for that as part of core mbed OS), but it's a model that only works if everyone in the ecosystem plays along. When we tried to enforce the evented model as part of mbed OS 3 we ran into many problems, because it broke compatibility with a lot of libraries that were not written with the event loop in mind. This is the same problem that server-side languages faced before, where early attempts at event-driven development like Python's Twisted or Ruby's EventMachine never gained a large following, in part due to incompatibilities with the existing ecosystem. Node.js - without an existing ecosystem of synchronous libraries - however managed to popularize the event-driven programming model.

Battery life

Event loops give us an additional benefit. When we’re aware of all scheduling, our OS is allowed to take over the sleep management schedule on your device. If we need to do sensor readings every 5 minutes, we can schedule this event and put the MCU in deep sleep in between. Combined with drivers implementing wake locks (for a reference of how this could work, see here), and furthering our work on tickless idle mode, we could get good sleep mode out of the box for many scenarios.

Cheaper firmware updates

One of the difficult problems in building IoT devices is encountered while patching devices in the field. For compiled code, it requires enough flash to hold two versions of the firmware as well as a bootloader which knows about firmware updates. The firmware is binary, which means it also generates relatively large diff files, even for small code updates. bsdiff generates 3-4 Kb diff files even for single line changes - while also being memory hungry. This makes firmware updates impossible on constraint networking protocols like LoRaWAN.

When we run an interpreted language, our application logic is just text, separated from the native libraries and the VM. This makes it easy to send new firmware to the device (it's just text) and it compresses and diffs very well. This comes with the downside that you can only patch your application logic, but it can be combined with full device firmware update like we have in mbed Cloud to do cheap updates when possible, and full updates when necessary.

Developer tools innovations

A higher level language also allows us to think of new ways of writing code, by iterating faster on development tools. We have seen this with the BBC micro:bit project - in which a million children in the UK learned how to program as part of their primary school curriculum. The micro:bit comes with a visual block-based programming language in which students can create their program by dragging and dropping blocks together. On top of this the (in-browser) IDE also has a simulator present, which shows a simulated micro:bit running the program that was just created.

The simulator provides a fantastic way of shortening the develop test feedback loop from minutes to seconds. This is all enabled by the abstraction layer that the block editor provides. The same blocks can be compiled down to C++ to run on a device, or interpreted by the browser and be used to visualize the program in the simulator.

BBC micro:bit simulator

BBC:microbit Block Editor, with simulator running on the right side of the screen.

The key is that the abstraction is high enough that the user just sees peripherals. For the micro:bit, we want to have a 'Screen' peripheral, rather than simulating the I2C bus. This abstraction layer in a low-level language is much harder to enforce (although not impossible, of course). In addition, if we write application code in JavaScript (rather than MicroPython for example), we can even run the simulator completely in the browser. One of the first things we did for JS on mbed was to develop an in-browser simulator that supported Bluetooth.

REPL

One of the other models that an interpreted language provides is the ability to run a Read-Eval-Print-Loop (REPL) on a device. With an REPL, we are given a shell in which we can interact with the device dynamically, without having to recompile code or flash new firmware. This allows for very flexible prototyping. Not sure if the datasheet is right for an external peripheral? Just use the REPL to quickly fire some I2C or UART messages to it. Again, this is about shortening the feedback loop and is also very useful if your actual application will be written in C++. Our REPL for JS on mbed is located here.

REPL

We might be able to take this further, offering pre-built images containing the core OS and a networking stack. When the device comes online, it can be programmed straight from the online compiler, storing the application code in flash. This could reduce the tools overhead for new developers even more.

Cost might be worth it (in the near future)

At the moment, software development is a relatively small part of the cost when building IoT devices; hardware design is the expensive part - especially because it's easier to patch software than hardware. Having to spend a dollar extra to add more RAM or flash to your device, because you're shipping it with a JavaScript VM, will drive up cost faster than the development time savings are worth.

But, times are changing. With the advent of cheap modules, which combine MCUs and radios, the upfront cost of designing connected devices is being driven down by not having to pay an RF engineer or do certification for your device. Although this raises the price per unit, it will become feasible to do smaller runs of devices, creating a market where smaller companies can also produce (relatively) low-cost custom IoT solutions.

If you can save time developing your firmware by using a higher level language (at the price of a beefier MCU), it might be cost-effective at low volumes. With the price of modules constantly dropping (here's a $2 WiFi module with a Cortex-M3 and 512K RAM), the cost effectiveness threshold will go up over time.

Conclusion

While JavaScript (and other high level languages) on microcontrollers have downsides, they also allow us to try interesting new programming models. For instance, we can create new development tools that shorten the develop-test feedback loop like REPLs or simulators. We can also abstract away common problems that embedded developers have, like dispatching from ISRs to the main thread. In addition, it allows us to experiment with new features, like automatic sleep management.

For the majority of IoT devices, C/C++ will still be the way to go. However, JavaScript on your microcontroller will be a great prototyping tool, or maybe even a proper and cost-effective choice for teams doing smaller runs of hardware.

For more information on JavaScript on mbed, visit mbed.com/js, or watch the introduction of the project at JSConf.asia 2016 here:

-

Jan Jongboom is Developer Evangelist IoT at ARM. He has written about building hardware before.

mbed Connect Asia 2016: Bringing IoT back to the birthplace of devices

The ARM mbed team wrapped up an incredibly busy 2016 last week, with our first ever mbed Connect Asia event, held in Shenzhen, China. During one action-packed day, we mingled with our developers and our partners, hosted two parallel technical tracks and celebrated mbed’s success throughout Asia.

/media/uploads/dirons/20161205_073710_s.jpgEarly birds: Our huge conference room, ready for our delegates!

With more than 500 attendees from across Asia, mbed Connect Asia was our biggest event yet. After a kick-off and welcome to the event, mbed’s Deputy GM Michael Horne delved deep into mbed’s vision and strategies for building the next generation IoT and how it will impact our global ecosystem. Gartner predicts that “6.4 billion devices will be connected worldwide by the end of 2016,” with huge future growth in many segments, including Smart Home, Smart City and Industrial spheres. Device management will be an enormous challenge in IoT adoption, and ARM has addressed this issue by expanding the mbed IoT Device Platform to include our newly launched IoT device management solution, mbed Cloud. To learn more about mbed Cloud, visit cloud.mbed.com and read about the announcement here.

/media/uploads/dirons/img_20161205_121154.jpg Our mission is to enable IoT developers to succeed, efficiently and securely

Following an insightful industry panel featuring experts from Baidu, Lierd, Minewtech, ST Microelectronics, Seeed Studios and the Xiamen IoT Research Center, we officially welcomed Minewtech as an mbed Partner!

Our technical track sessions were held after lunch, with two themes spanning across the whole mbed ecosystem. Developers were invited to learn more about mbed OS directly from the knowledge experts, including how to ensure that IoT devices are designed securely from the foundation, an essential requirement for the next generation IoT.

In the second track, a wide spectrum of IoT was addressed, from end-points through to the mbed Device Connector, as well as how developers and partners can collaborate and utilize our diverse ecosystem in order for everyone to succeed. The IoT cloud was one of the hottest topics of the event, bringing a more holistic approach to IoT solutions and expanding areas of interest from the creation and development of devices, all the way through to managing any device on any cloud.

/media/uploads/dirons/mbed_connect_asia_collage_final.jpg Highlights throughout the day

Thank you and 谢谢 to all of our attendees, we hope to see you again next year!

Download mbed Connect presentations:

Track 1:

Track 2:

Reducing memory usage by tuning RTOS configuration

Two weeks ago, we blogged about optimizing memory usage for mbed OS 5.2, and today we want to show how memory usage can be decreased even further. This can be accomplished by tuning the RTOS configuration to our specific needs, or even turning off mbed RTOS altogether. This allows us to fit mbed mbed OS 5.2 on the smallest targets, like the nRF51822 which has only 6K of RAM available for user-space applications.

All programs in this blog post are compiled for the nRF51-DK target using GCC 4.9.3.

Baseline numbers

When compiling blinky with NDEBUG defined and stdio-flush-at-exit disabled (see this blog post), we use about 5K of static RAM and 15K of flash:

Total Static RAM memory (data + bss): 5044 bytes
Total RAM memory (data + bss + heap + stack): 20548 bytes
Total Flash memory (text + data + misc): 14896 bytes

RTOS configuration

If we don’t need all the features of mbed RTOS, we can tweak these numbers by reducing the number of tasks, decreasing thread stack sizes or disabling user timers. The default values for these configuration options are in this file and can be overriden using the mbed_app.json configuration file.

Some interesting options that we have here are:

  • OS_TASKCNT - Number of concurrent running user threads.
  • OS_IDLESTKSIZE - Default stack size for the Idle thread - in words of 4 bytes.
  • OS_STKSIZE - Default Thread stack size - in words of 4 bytes.
  • OS_TIMERS - Enable the timer thread.
  • OS_FIFOSZ - ISR FIFO Queue size.
  • OS_MUTEXCNT - Maximum number of system mutexes.

We can configure these options by adding the ‘mbed_app.json’ file in the root of our project and re-compiling:

{
    "macros": [
        "NDEBUG=1",
        "OS_TASKCNT=1",
        "OS_IDLESTKSIZE=32",
        "OS_STKSIZE=1",
        "OS_TIMERS=0",
        "OS_FIFOSZ=4",
        "OS_MUTEXCNT=1"
    ],
    "target_overrides": {
        "*": {
            "platform.stdio-flush-at-exit": false
        }
    }
}

The actual parameter values will of course depend on your application. With these configuration parameters above we can shrink down our static RAM usage to about 3K:

Total Static RAM memory (data + bss): 3364 bytes
Total RAM memory (data + bss + heap + stack): 20548 bytes
Total Flash memory (text + data + misc): 14324 bytes

Removing the RTOS

If we don’t need the RTOS at all, we can also remove the feature completely, saving both RAM and flash. To do this, we can exclude the RTOS folders when building via an .mbedignore file. We will also need to change main.cpp to not use Thread::wait as this function will not be available.

To remove the RTOS we create a new file ‘.mbedignore’ in the root of our program:

mbed-os/rtos/*
mbed-os/features/FEATURE_CLIENT/*
mbed-os/features/FEATURE_COMMON_PAL/*
mbed-os/features/FEATURE_UVISOR/*
mbed-os/features/frameworks/*
mbed-os/features/net/*
mbed-os/features/netsocket/*
mbed-os/features/storage/*
mbed-os/events/*

When we now rebuild the application we see a huge decrease of RAM and flash, to 720 bytes (!) static RAM and less than 8K of flash.

Total Static RAM memory (data + bss): 720 bytes
Total RAM memory (data + bss + heap + stack): 20552 bytes
Total Flash memory (text + data + misc): 7828 bytes

Conclusion

The flexibility of mbed OS 5's configuration system makes it possible to run the OS on many different MCUs, whether they're beefy Cortex-M4s or tiny Cortex-M0s. For small targets, it's always worth it to carefully tune the RTOS parameters and if you have a very small target and a specific use case, you might even consider removing the RTOS altogether. By using the optimizations in this post, we managed to squeeze mbed OS 5, a Bluetooth stack, mbed TLS and Google's Eddystone reference implementation onto a tiny nRF51822 beacon.

-

Jan Jongboom is Developer Evangelist IoT at ARM.

Optimizing memory usage in mbed OS 5.2

Three months ago we released mbed OS 5, the latest version of our operating system for microcontrollers. While we added a lot of new features - including an RTOS - we also saw a bigger than expected increase in flash and RAM usage, two things that are scarce on embedded devices. Reason for Vincent Coubard, Senior Software Engineer on the mbed team, to dig through the .map files and see how we can decrease memory usage in mbed OS.

Comparison with mbed 2.0

First, we need some baseline numbers. When compiling blinky - a simple program that just flashes an LED - on mbed 2.0, we see about 5K static RAM, and around 38K flash used (compiled with GCC 4.9.3 on the FRDM-K64F):

Allocated Heap: 65536 bytes
Allocated Stack: 32768 bytes
Total Static RAM memory (data + bss): 5128 bytes
Total RAM memory (data + bss + heap + stack): 103432 bytes
Total Flash memory (text + data + misc): 37943 bytes

When we compile the same program on mbed OS 5.1.2 we see a large increase in both RAM and flash usage, to almost 13K static RAM, and about 57K flash:

Allocated Heap: 65536 bytes
Allocated Stack: unknown
Total Static RAM memory (data + bss): 12832 bytes
Total RAM memory (data + bss + heap + stack): 78368 bytes
Total Flash memory (text + data + misc): 57284 bytes

Removing unused modules

To see where that memory went we can first look at how memory usage is split between different modules:

+---------------------+-------+-------+-------+
| Module              | .text | .data |  .bss |
+---------------------+-------+-------+-------+
| Fill                |   132 |     4 |  2377 |
| Misc                | 28807 |  2216 |    88 |
| features/frameworks |  4236 |    52 |   744 |
| hal/common          |  2745 |     4 |   325 |
| hal/targets         | 12172 |    12 |   200 |
| rtos/rtos           |   119 |     4 |     0 |
| rtos/rtx            |  5721 |    20 |  6786 |
| Subtotals           | 53932 |  2312 | 10520 |
+---------------------+-------+-------+-------+

Most of this is normal; we're loading the hardware abstraction layer and the RTOS, but we also see features/frameworks. That is weird, as that is where our test tools live. We happen to build one of our test harnesses into every binary. What a waste! By eliminating this module we save about 1K of RAM and a whopping 8K of flash:

Total Static RAM memory (data + bss): 11808 bytes
Total RAM memory (data + bss + heap + stack): 77344 bytes
Total Flash memory (text + data + misc): 49807 bytes

Printf and UART

The next target would be the Misc module with around 28K of flash used. When we look at a visual representation of the memory map for our program, we see the UART driver and various functions related to printf being compiled in. That is suspicious, given that we are not using either in our program.

/media/uploads/janjongboom/memory1.png

Visualization of our memory map showing the UART and printf functions in the top right corner.

We found that this was related to how we do traces and assertions in some of our modules, always redirecting error messages to printf. Whenever someone uses a single printf we need to compile in both the library and the UART driver (for serial communication). That is a huge overhead for something that is not actually used. While traces and assertions are very useful during development and in debug builds, we want them completely removed in release builds.

We already complied with standard C by not tracing in assertion code (assert and MBED_ASSERT functions) when NDEBUG is defined, but still wrote traces in error functions. By altering our drivers (1, 2) to fully disable logging to serial output on errors when NDEBUG is defined, we save 28K(!) of flash (but no RAM):

Total Static RAM memory (data + bss): 11808 bytes
Total RAM memory (data + bss + heap + stack): 77344 bytes
Total Flash memory (text + data + misc): 21244 bytes

To disable this feature you need to set the NDEBUG macro and the following configuration parameter in your mbed_app.json file:

{
    "macros": [ 
        "NDEBUG=1"
    ],
    "target_overrides": {
        "*": {
            "platform.stdio-flush-at-exit": false
        }
    }
}

Some more information can be found in this comment.

Note: Different compilers, different results; when compiling with ARMCC the printf and UART libraries only cost 14K of flash.

No need for destruction

We can also take advantage of the fact that we run our programs only on embedded targets. When you run a C++ application on a desktop computer, the runtime constructs every global C++ object before main is called. It also registers a handle to destroy these objects when the program ends. This is injected by the compiler and has some implications for the application:

  • The code injected by the compiler consumes memory.
  • It implies dynamic memory allocation, and thus requires malloc and friends to be included in the binary, even when not used by the application.

When we run an application on an embedded device we don't need handlers to destroy objects when the program exits, because the application will never end. By removing the registration of destructors on application startup, and by eliminating the code to destruct objects when exit() is called, we can shave off another 2.5K of RAM and an additional 8K of flash:

Total Static RAM memory (data + bss): 8008 bytes
Total RAM memory (data + bss + heap + stack): 73544 bytes
Total Flash memory (text + data + misc): 14102 bytes

Conclusion

Together these three optimizations gave us a huge decrease of both static RAM (47%) and flash (2.69x less) usage. Compared with mbed 2.0 we use 3K more RAM - which is mainly due to the inclusion of mbed RTOS - but we use only half the flash. We'll continue to make improvements on this in the near future. All patches have landed and are included in mbed OS 5.2.

-

This article was written by Vincent Coubard (Senior Software Engineer) and Jan Jongboom (Developer Evangelist IoT).