Category Archives: Blog

IKEA Floalt Light Panel Repair

Finally, I have come full circle on this repair that started five years ago when one of my IKEA FLOALT light panels (L1529, 60×60 cm) suddenly started flickering. It has long been gathering dust, but I finally dug up this old Reddit post with a suggested repair that I had bookmarked:

https://www.reddit.com/r/tradfri/comments/ac9vqr/blinking_flickering_floalt_panel/

So credit where credit is due, I did not come up with this repair on my own but I figured I could take some more photos and add another mention of it on the Internet so hopefully more panels can be fixed and saved from being thrown away.

The first step was to remove the LED driver from behind the panel. (I did this 2-3 years ago, but you don’t have to wait that long.)

๐Ÿ’€Note: If the driver was just plugged in (and not on a shelf for years), there can be still be high voltage across some of the capacitors. Only proceed if you know what you are doing!

Inside the case is the driver board. It can be lifted out and turned over to reveal the culprit – R6. I verified with a multimeter that it had indeed failed open and no longer measured 47 Ohms as it should.

R6 removed.

R6 is a 47 Ohm 1206 resistor and after replacing it with a new one, the LED driver was assembled back together. Luckily this was all that was required, the panel now works fine again!

Since this seems to be a widespread problem, the current through R6 is most likely above its rating. If it fails again, I will consider looking for 1206 resistors with a higher current rating. I have also heard that one can stack two SMD resistors on top of each other, effectively putting them in parallel, sharing the current. A couple of 100 Ohm resistors would probably be close enough.

Now go repair your IKEA FLOALT light panel!

BLE in Embedded Rust

I wanted to make a Bluetooth widget and I wanted to learn more Rust, so I went about looking what options there are for enabling BLE in an Embedded Rust project.

First off, I could probably have done this on a Raspberry Pi and saved myself a few headaches, but I specifically want to get into “bare-metal embedded”, not “embedded-Linux embedded”.

Hardware-wise, the most well-supported option in the community at the time of writing (fall 2021) seems to be the Nordic nRF52. I already got the nRF52840 devkit to work through the Knurling Sessions, so that’s what I was going to use.

The way Nordic’s BLE chips work is they have a binary blob called the SoftDevice which implements the higher layers of the BLE stack and interfaces to the hardware. It is (presumably) well integrated in the Nordic C SDK, but in order to use it from Rust, we need an interface. After some research, I found three good options and a few worse ones:

1. nrf-softdevice

The best option that I have found is nrf-softdevice by the Embassy project. Embassy is an async executor for embedded Rust, what that means will have to be the topic of a separate post. What they have done is generate Rust bindings for the Softdevice binary. The project is work-in-progress and consists of several crates in the one git repository linked above, no packets available on crates.io. Being related to Embassy, nrf-softdevice relies on a lot of async/await. If this is not what you want, this is not the solution for you.

2. Rubble

Rubble is an entire BLE stack built in Rust, meaning it replaces the Nordic Softdevice entirely. It is being developed on the nRF52 and seems to have an impressive feature set for a community project. Some features are still missing however, and it is not certified for use in commercial products. If the BLE widget I’m building ever turns into something that I would like to sell, I might have to re-write all of the code or pay for having Rubble certified..

3. Nordic SDK

A third option mentioned a lot is to flip things around and include a Rust application binary into a C project using Nordic’s official SDK. One will need to implement whatever glue-code is needed to wire in the Softdevice to the application, but then the rest can be done in Rust. While not as exciting as the other two options, it seems like this is what experienced professional embedded developers are doing to bring Rust into production projects today.

Honorary mentions

If none of the solutions above sound interesting, one might also interface the Softdevice directly. For this, get a hold of the Nordic SDK and dig in to find the relevant memory locations to call for the various APIs.

Another option that might be worth mentioning is to get a complete BLE module that speaks some form of AT commands over UART. These exist from many different vendors and pushes all the BLE logic out of the application. The downside is one instead has to deal with a lot of string manipulation to handle the AT interface.

Did I miss any?

Bringing Back the Stellaris Evalbot

The Stellaris Evalbot devkit
The Stellaris Evalbot devkit

TLDR; https://github.com/albertskog/stellaris-evalbot-fw

In my pile of old development kits is this little robot from Texas Instruments. The microcontroller on it is the LM3S9B92, an old (and, I seem to recall, fairly buggy) ARM Cortex-M3 chip. It must have gathered dust for around ten years at this point, but I did not want to throw it away. Instead, let’s see if we can bring it back to life!

I found some 4-11 year old projects on Github but was not able to make them compile. I also had some old training material from the course where I got the bot, but it was missing the TI StellarisWare SDK. Everywhere I looked online, including TI’s own E2E forum, the links to StellarisWare were dead since this family of chips has been deprecated and out of production for a long time. I finally found what seems to be the last working download link on the Internet:

๐ŸŽ‰๐ŸŽ‰ https://www.ti.com/tool/SW-DRL#downloads ๐ŸŽ‰๐ŸŽ‰

I briefly tried to get Texas Instrument’s own IDE Code Composer Studio to recognize this historical artifact but soon gave up since I much prefer to work in VSCodium anyway.

With the repositories I found online and an example project in StellarisWare I was finally able to compile some code by simply running make in a terminal (on my computer running Ubuntu 20.04). To get this working, you also need the gcc-arm-none-eabi suite of GCC tools for ARM installed (see Github repository for more instructions).

Luckily the LM3S9B92 is supported by OpenOCD, so after some trial and error, figuring out the location of some configuration files that had moved, I was finally able to connect to the board with openocd -f evalbot.cfg.

Contents of evalbot.cfg:

source [find interface/ftdi/luminary.cfg]
source [find target/stellaris.cfg]

I also created an alternative file evalbot_flash.cfg to flash a binary directly:

source [find evalbot.cfg]

init
halt
flash write_image erase ./gcc/project.bin
reset
shutdown

With this in place, I could create a workspace in VSCodium (you might prefer Visual Studio Code) and add a few tasks to .vscode/tasks.json:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "make",
            "type": "process",
            "command": "make",
            "args": [],
            "problemMatcher": ["$gcc"],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "label": "upload",
            "type": "process",
            "command": "openocd",
            "args": ["-f", "evalbot_flash.cfg"],
            "problemMatcher": ["$gcc"],
            "dependsOn": ["make"]
        },
        {
            "label": "clean",
            "type": "process",
            "command": "make",
            "args": ["clean"],
            "problemMatcher": ["$gcc"]
        }
    ]
}

Investigating a bit more, I found it’s even possible to do interactive debugging! You will need the Cortex-Debug extension for this:

VSCodium: https://open-vsx.org/extension/marus25/cortex-debug
VSCode: https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug

And then add this to .vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "cortex-debug",
            "request": "launch",
            "name": "Debug",
            "servertype": "openocd",
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "make",
            "runToMain": true,
            "executable": "${workspaceFolder}/gcc/project.axf",
            "device": "LM3S9B92",
            "configFiles": ["evalbot.cfg"],
            "preRestartCommands": [
                "monitor reset halt",
                "break main"
            ]
        }
    ]
}

With this, you should now be able to launch a debug session!

With this in place, I made a basic robot example that drives around and avoids obstacles. I had come across a bunch of “helper” functions that look like they were distributed with the kit back in the day. One thing to look out for if you use them yourself is the function MotorSpeed(tSide ucMotor, unsigned short usPercent). โš ๏ธ Be warned! โš ๏ธ The parameter usPercent is not in percent, it is percent “in fixed point 8.8 notation”, meaning you need to shift your percent value 8 bits: speed = 50 << 8! Using the value 50 directly will in fact give you a tiny tiny speed that is not enough to make the wheels turn.

I gathered the code and some more detailed instructions in a Github repository. At the time of writing it is just a basic example as mentioned, but I might add more features in the future! Here it is:

https://github.com/albertskog/stellaris-evalbot-fw

Doing Knurling Sessions 2020 Q4 in 2021 Q2

Nordic nRF52840-DK used in the Knurling Sessions

I’m going through Knurling Sessions 2020 Q4, an excellent course/project from the Knurling project. It was originally available only to sponsors of the project but is now open to everyone for free.

I decided to use the latest version of all crates instead of following the instructions to the letter. Here are some notes on things that have changed as of May 2021.

2.2.1.1. Hello World: while editing config.toml to set up the first project, the instructions show a flag that is no longer in the app template; probe-run no longer supports the --defmt flag, just skip it.

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-run --chip nRF52840_xxAA"

2.2.1.3. Internal Temperature Sensor: defmt has changed its syntax from {:f32} to {=f32}.

let temperature = temp.measure().to_num();
defmt::info!("{=f32}", temperature);

2.2.2.1. Hello Sensor: need to add .into_floating_input() when declaring scl and sda.

let scl = pins.p0_30.degrade().into_floating_input();
let sda = pins.p0_31.degrade().into_floating_input();

Find the type of a variable in Rust

I was trying to make an abstraction in Rust and found myself navigating a lot of code to determine the type of pwm below so I could put it in a struct:

let mut pwm = Timer::tim2(dp.TIM2, &clocks, &mut rcc.apb1)
    .pwm::<Tim2NoRemap, _, _, _>(c1, &mut afio.mapr, 1.khz());

A quicker way to find it is to add an empty type, : () and check the compilation error:

let mut pwm: () = Timer::tim2(dp.TIM2, &clocks, &mut rcc.apb1)
    .pwm::<Tim2NoRemap, _, _, _>(c1, &mut afio.mapr, 1.khz());

I’m using Visual Studio Code with the Rust Analyzer plugin and there I immediately get the error and can copy the type by hovering on the line:

So there you have it, the type was as simple as this – no wonder I gave up on trying to find it in the code! ๐Ÿ˜…

type PwmType = hal::pwm::Pwm<
    hal::pac::TIM2,
    hal::timer::Tim2NoRemap,
    hal::pwm::C1,
    hal::gpio::gpioa::PA0<hal::gpio::Alternate<hal::gpio::PushPull>>,
>;

How to close an unresponsive SSH session

Enter, Tilde, Period
Enter, Tilde, Period

If you work on embedded Linux systems, those Docker container thingies, play around with Raspberry Pi or anything in between, you may occasionally find yourself on your main computer connected to the target via SSH when it suddenly hangs or gets rebooted, rendering your SSH session unresponsive. Sometimes when this happens, the SSH session gets disconnected automatically and you can start a new one. In other cases, the terminal with SSH open freezes and your usual hotkeys like ctrl + D do nothing. This is because ctrl + D key presses are usually transferred to the target and the disconnecting is so to say engaged from there.

If only there was a hotkey that was detected by the SSH client before it tries to send it to the target ๐Ÿค”. You guessed it, there is! The sequence โŽ + ~ + . lets you disconnect from an unresponsive target without having to close the terminal or anything like that. To be clear, you press the keys one by one, no need to hold them all at once.

It does not work!

I had some troubles getting this to work reliably. Two things that helped me:

  • Realizing the Enter key is part of the sequence. The first time I read about this, the sequence was said to be just Tilde + Period. Often when working in a terminal, Enter is the last key that was pressed so this works fine most of the time. Remember Enter as part of the sequence!
  • Remembering how to type Tilde on my keyboard layout. Because I use a Swedish keyboard, Tilde is actually alt + ยจ , space, where ยจ is the key with ^ and ยจ on it. Since there is no visual feedback when typing in the escape sequence (which is now five key presses), I would sometimes get it wrong.
Tilde on a Swedish keyboard is actually Alt + ยจ , Space

Strict Aliasing – yet another way for C-code to blow up

Recently, I got to learn about Strict Aliasing in C. It is yet another thing that can cause your C code work perfectly fine today and then blow up because of Undefined Behavior down the line. One example of what not to do is casting an array of uint8_t (like a payload from a communications protocol) into a struct (like the message you are receiving):

void receive_data(uint8_t * payload, uint16_t length) {
    ... // Sanity checking etc
    my_struct_t * my_struct = (my_struct_t *) payload; // Don't do this!
    do_stuff(my_struct->some_field);
}

A better way is to use memcpy:

void receive_data(uint8_t * payload, uint16_t length) {
    ... // Sanity checking etc
    my_struct_t my_struct;
    memcpy(my_struct, payload, sizeof(my_struct_t)); // Do this instead!
    do_stuff(my_struct.some_field);
}

One reason this kind or “reinterpret cast” is not allowed is that you can’t be sure that accessing a field within the struct after typecasting will be a properly word-aligned memory access.

For more details, here is a write-up with more examples which also explains the situation for C++: https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8

Embedded Rust Toolchains

I recently started learning Embedded Rust. As I mentioned at the top of the last post, there are a couple of toolchain options:

  • OpenOCD + GDB
  • probe-rs + cargo-embed

Turns out there is also a third one that I just discovered:

  • probe-rs + probe-run

This was a little confusing at first, I was unsure what was the better option and how these projects all connect to each other. Embedded Rust seems to be moving fast, so this might get outdated but here is a basic summary if you are just getting into Embedded Rust as well.

The Embedded Rust way

Rust-embedded is a working group in the official Rust organization. Among other things, they maintain The Embedded Rust Book, which you may have come across. In the book, they describe what I would call the “official” toolchain, using OpenOCD and ARM-compatible GDB (gdb-multiarch). This is probably the way to go if you need to do serious development today. OpenOCD and GDB are stable and mature projects and for a Rust programmer, it is the most fully-featured and reliable option right now.

Enter probe-rs

As an alternative to those external (non-Rust) dependencies, a team has formed an ambitious project around replacing it all with software written in Rust – probe-rs. Here is an illustration (borrowed from the video below):

This illustration shows nicely what probe-rs tries to be.

Here is a very informative talk by one of the people behind probe-rs:

My major learning from this talk was that probe-rs is really a library. Other projects, like cargo-embed and probe-run are built on top.

Cargo-embed

The probe-rs team built cargo-embed to show off the capabilities of probe-rs. As such, it was the first tool I came across when I found the official probe-rs website. One might imagine that being built by the same team, cargo-embed will stay closer to the latest features of probe-rs and have a shorter path to get new features in. But this is just speculation.

To build and upload programs, you simply run cargo embed --release (see my last post about why --release is important for timing). It possible to do logging with rtt, a debugger-based thing that uses an internal buffer that gets read out by the debugger instead of, for example, printing over UART. Debugging is also supported (but not at the same time as rtt currently) from the command line, or visually in something like Visual Studio Code, by hooking into the GDB stubs that probe-rs provides. This is an interface that (to the best of my understanding) mimics GDB’s, but actually goes directly to probe-rs.

Configuration is done in a new file called Embed.toml. There you configure what chip you are using and whether to use rtt or GDB debugging, or set up separate profiles for each.

The vision of probe-rs is to offer a full development environment for embedded Rust, so they are also working on a VSCode plugin. It is still in alpha, and I have not tried it yet.

Probe-run

Ferrous Systems is a company that pops up everywhere in embedded Rust. They are a consultancy specializing in Rust for embedded applications and are also very active in open source development for embedded Rust. They started a project called Knurling dedicated to improving the experience working with embedded Rust.

Knurling has many sub-projects and probe-run is one of them. Built on top of probe-rs, it gives you the same features as cargo-embed, but in a slightly different packaging. The philosophy is that embedded development should work the same way as native development, so instead of introducing a new cargo command, probe-run is a so called Cargo runner. This means you configure the “usual” cargo run command to use probe-run under the hood. And there is no new configuration file to keep track of, just the regular Cargo.toml and .cargo/config.toml. Does it matter? Up to you.

Knurling also has an interesting logging framework; defmt. Instead of doing string formatting on the embedded device, it relies on a tool on the host side and simply generates a list of strings at compile time that is kept on the host. The embedded device then simply sends the index into that list (using rtt), causing much less overhead.

I do like the idea of keeping the main Rust interface unchanged, which speaks in favor of probe-run, but I’m not sure about plans for integrating probe-run with VSCode, or debugging with breakpoints. As I learn more, I hope to find a favorite and maybe also start contributing myself.

Embedded Rust: Timer Timeout Problem

TL;DR: When doing timing critical stuff, use the --release flag to get a faster binary!
For example: cargo embed --release.

I’m learning Embedded Rust on a STM32 Bluepill board (with a STM32F103 microcontroller). At the time of writing there seems to be two toolchain options:

  1. The “official” Embedded Rust way, using OpenOCD and ARM-compatible GDB.
  2. Up-and-coming probe-rs that is working on having everything in Rust and installable via cargo. Their tool cargo-embed basically replaces OpenOCD and GDB.

OpenOCD + GDB is true and tested, but a lot more work to set up. Probe-rs is litteraly just cargo install cargo-embed, but it is a work in progress and far from feature-complete. I tried both, but this particular thing caught me while using cargo-embed, so that’s the command I will be showing.

The Timer Problem

I wanted to talk to a ws2812 adressable RGB LED (also known as NeoPixel). I found the crate smart-leds that seemed perfect. It comes with several “companion crates” with device drivers that support different LEDs and several options for different ways of driving the ws2812, like the ws2812-spi and ws2812-timer-delay.

The SPI crate unfortunately did not work in my attempts so far. It manages to write to my LED once, then panics with the error “Overrun”. Probably I’m using a newer version of the embedded-hal and/or stm32f1xx-hal than it was written for. Maybe a topic for another day.

The Timer Delay crate also did not work at first. I broke out my Analog Discovery 2 to look at the data signal:

Delta-X column below the graph shows the time between the red lines – right above 200 us.

The time between bits was around 200 us. To get a comparison, I fired up a Platformio project for the same STM32 Bluepill board and imported Adafruit’s Neopixel library. Now, the LED of course worked perfectly and the problem was obvious:

With Adafruit’s Arduino library, the time between pulses is around 1.4 us.

The time between the bits was now only around 1,4 us. I will spare you the details of all the things I tried while wrongly thinking either the entire MCU or the timer was running at the wrong frequency.

The solution turns out to be almost silly: Rust binaries can be really slow if you do not compile them in release mode. Just add the --release flag and all is well! ๐Ÿ’ฉ

Solution:

cargo embed --release

There is apparently a way to override this per-dependency in Cargo.toml, that might be worth a try if you need it.

Update:

I tried adding the following to Cargo.toml to make all dependencies build with the highest optimization level, but this still was not enough to make the LED work in my case.

# Cargo.toml 
[profile.dev.package."*"]
opt-level = 3

I also tried increasing the optimization level for the whole dev profile. This worked already from level 2:

# Cargo.toml 
[profile.dev]
opt-level = 2

Stepping through code compiled like this with a debugger might not work as well though so you might as well use the release profile all the time and only drop down to dev for debugging.

NB-IoT and LTE-M Covarage Maps

Here are some links to coverage maps for NB-IoT and LTE-M in Scandinavia. The GSMA also has a global deployment map here:
https://www.gsma.com/iot/deployment-map/

Denmark

Finland

Norway

Sweden