A Zephyr RTOS based TTN Lorawan node

My previous post about Zephyr RTOS sample project with the STM32 blue pill board and the LMIC related posts such as Setting the SPI interface for Lorawan TTN LMIC and Some quick notes about Lorawan LMIC Library where the necessary stepping stones to enable the prototype creation for a TTN (The Things Network) Lorawan node but this time using the Zephyr RTOS and the LoraMac Lorawan library for the network connection.

Specifically for the LoraMac Lorawan node library, a Zephyr RTOS LoraMac code port exists, so it provides a device driver/API to the Zephyr applications which enables them to have Lora/Lorawan connectivity.

The Arduino framework and LMIC library was used as a comparative solution while debugging and testing the settings and connections for a successful connection from the node to the TTN network, to make sure that all configuration was correct and the radio (a RFM95W module) was functional. So during testing I went back and forth between the two libraries: LMIC using the Arduino framework and LoraMac-Node with Zephyr RTOS during testing.

The boards used for testing where the STM32F103CB BluePill board, the STM32F407VE board and the Blackpill STM32F411CE board. All boards can be used with either the Arduino framework and the Zephyr RTOS but I settled on doing most of all the tests with the Blackpill F411 board.

This choice was made because the main issue with the BluePill is that it doesn’t have enough flash space to have the Zephyr RTOS, the USB driver for console logging through USB and the LoraMac-node code and sample program to be stored. Using LMIC and Arduino framework on Bluepill is possible and leaves some space, but with Zephyr RTOS it is only possible to do anything useful but without USB support for serial logging. Still just note that modifying the Zephyr project configuration to have the USB driver from removed, and therefore no console logging through USB, allows to flash the board, but at the end not too much space is left do anything useful with this board. So at the end I stopping doing any tests with the Bluepull and not used it further.

Both the two other boards, the F407 and F411, have more than enough space to do any testing, but the smaller form factor of the F411Blackpill  is ideal size to do the tests (it is breadboard friendly), so I ended up using the F411 for all tests, which means that all the following steps are for this board, but can be easily replicated to other boards as long there is enough flash space.

Zephyr RTOS and Lorawan support:

The Lorawan Zephyr RTOS support was out more or less in October 2020, and for a single board 96b-wistrios board with no other information regarding any other possible boards. The support is possible by enabling the SX1276 radio driver and the Lorawan stack on the Zephyr project configuration file.

Zephyr RTOS feature support are enabled on the prj.conf file by defining the feature that we want to enable. So for this testing the following features where enabled:

# Enable GPIO pins and the SPI interface for 
# communicating with the RF95W module
CONFIG_GPIO=y
CONFIG_SPI=y

# Config USB support so that we can use a USB 
# to view log and debug information
CONFIG_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr Console"

# Point the console to the USB
CONFIG_USB_UART_CONSOLE=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_UART_CONSOLE_ON_DEV_NAME="CDC_ACM_0"

# Enable the console, logging and the printk function
CONFIG_CONSOLE=y
CONFIG_LOG=y
CONFIG_PRINTK=y

# Enable the SX12XX radio driver for Lora
CONFIG_LORA=y
CONFIG_LORA_LOG_LEVEL_DBG=y
CONFIG_LORA_SX12XX=y
CONFIG_LORA_SX1276=y

# Enable the Lorawan stack
CONFIG_LORAWAN=y
CONFIG_LORAWAN_LOG_LEVEL_DBG=y
# Define the Lorawan region to be used: CONFIG_LORAMAC_REGION_EU868=y #CONFIG_LORAWAN_SYSTEM_MAX_RX_ERROR=90

All the above configuration enables the necessary components for building the Lorawan node: the SPI bus, GPIO, the SX1276 radio driver and LoraWan stack and finally logging to the USB console, where we can just use a simple terminal program to see what is going on, by can also use the printk function for the old style printf debugging…

Note that at this point we’ve haven’t defined neither the board that we will be using and the hardware interface to the Lora radio module, since the target hardware is defined at build time, not at configuration time.

Connecting the Lora Radio Module:
I’m using a RFM95W radio module that exposes some pins but not all from the SX1276 radio. To correctly work the LoraMac node library requires that each SX1276 radio DIO pins has its own associated GPIO pin (we can not merge DIO pins using diodes for example). For the several SX1276 DIO pins for Lora support, the DIO0 and DIO1 must be connected to the processor GPIO pins. The pins can be connected directly without any pull-downs or pull-ups.

The SX1276 radio module, and also by definition, the RF95W radio module that uses the SX1276 radio, use the SPI interface for communication, and so we must select what SPI bus we will use (if the board supports several SPI buses), and connect the associated SPI pins from the selected SPI bus to the RFM95W SPI pins.

To do this we need to define which SPI bus is used and how the RFM95W module is connected.

The Zephyr RTOS uses device trees to specify the connected hardware in a portable method, and comes “out of the box” with a series of predefined configurations for the hardware such SPI, I2C, UART, PWM, LED’s and so on.

If we want to change or add something to the hardware configuration, we need to create what is called an overlay file which for a specific board instance it defines or reconfigures the hardware, and overlays the new configuration over the default provided one.

In our Lorawan node we will be using the Blackpill STM32F411CE board, and for this board the Zephyr RTOS board name is blackpill_f411ce which means that we need to create a new overlay file for selecting which SPI bus we will be using and what pins the RFM95W radio module pins will be using. This configuration must be set on a file named blackpill_f411ce.overlay, and as we can see the filename must math the board name. This file must reside on the root of the project side by side with the prj.conf file or under a sub-directory named boards.

In my testing the RFM95W module will be connected to the SPI1 bus and for this board the pins are taken from the following map:

Blackpill F411 pinout

For the SPI1 bus we have:

  1. MISO – PA6
  2. MOSI – PA7
  3. SCLK – PA5

And these are the default SPI pins for the SPI1 bus that, if we need can we change to other alternate SPI1 bus pins through the overlay file. For now we just use the default pins.
To connect the RFM95W module, we need to define at least the chip select pin NSS, the DIO0 and DIO1 pins being other pins optional. Unlike the LMIC library where we could use LMIC_UNUSED_PIN, it seems that there isn’t such alternative on Zephyr.

So the pin mapping is now:

  1. NSS – PB12
  2. DIO0 – PA0
  3. DIO1 – PA1
  4. RESET – PA2

With the above settings our overlay file has now the following configuration:

&spi1 {
       status = “okay”;
       cs-gpios = <&gpiob 12 GPIO_ACTIVE_LOW>;

       lora: sx1276@0 {
                compatible = “semtech,sx1276”;
                reg = <0>;
                label = “sx1276”;
               reset-gpios = <&gpioa 3 GPIO_ACTIVE_LOW>;
               dio-gpios = <&gpioa 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                    <&gpioa 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                    <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                   <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                   <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                   <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
                rfi-enable-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
                rfo-enable-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
                pa-boost-enable-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
               tcxo-power-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
               tcxo-power-startup-delay-ms = <5>;
              spi-max-frequency = <1000000>;
    };
};

/ {
     aliases {
         lora0 = &lora;
     };
};

But the above file has a lot of unused pins mapped to GPIOA pin 4, that is really not correct or ideal.  If checking the following file: ZEPHYR_BASE/zephyr/dts/bindings/lora/semtech,sx1276.yaml we can see that some definitions are not required, and hence we can simplify our hardware configuration to only the RFM95 module pins that we really use:

&spi1 {
   status = “okay”;
   cs-gpios = <&gpiob 12  GPIO_ACTIVE_LOW>;

   lora: sx1276@0 {
       compatible = “semtech,sx1276”;
       reg = <0>;
       label = “sx1276”;
       reset-gpios = <&gpioa 3 GPIO_ACTIVE_LOW>;
       dio-gpios = <&gpioa 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                            <&gpioa 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
       spi-max-frequency = <1000000>;
       power-amplifier-output = “pa-boost”;
   };
};

/ {
   aliases {
      lora0 = &lora;
 };
};

And this ends the hardware configuration for Zephyr RTOS with the RFM95W module on SPI1 bus.
We are now able to build our Lorawan TTN node for the Blackpill F411 board.

Building the Lorawan node code:
For building the Lorawan node code we must first installed and configured Zephyr RTOS so that we can use the west tool.

The code for the example is available here, and is derived from the class_a Lorawan Zephyr example, with added led blinking and USB logging so we can see what is going on. Beware that we need to first configure first the TTN keys (see below).

git clone https://github.com/fcgdam/zLorawan_Node
workon zephyr 
cd zLorawan_Node
west build -b blackpill_f411ce -p
west flash --runner openocd && sleep 2 && screen /dev/ttyACM0

For flashing I’m using the STLink connected to the board SWD pins, and since the Zephyr default for this board is DFU, we need to specify that we want to use StLink to flash it through the option –runner openocd.

We might need to press the reset button on the board so that the west flash tool works. A simple workaround to this is to edit the openocd.cfg file at (…)/zephyrproject/zephyr/boards/arm/blackpill_f411ce/support/ and add the reset_config none to the file:

source [find board/stm32f4discovery.cfg]
reset_config none

$_TARGETNAME configure -event gdb-attach {
    echo "Debugger attaching: halting execution"
    reset halt
    gdb_breakpoint_override hard
}

$_TARGETNAME configure -event gdb-detach {
    echo "Debugger detaching: resuming execution"
    resume
}

With this modification, flashing should work now without the need to press the reset button.

TTN Configuration:
Unlike the LMIC library, the key values for the necessary keys are taken directly in LSB format from the TTN console. So no need to convert anything to MSB format.
The Application EUI is called now JOIN_EUI and that is the value that we should put on that #define LORAWAN_JOIN_EUI.

Sample node output:
Reseting and connecting to the usb port provided by the board we now have the following output:

[00:00:00.130,000]  sx1276: SX1276 Version:12 found
[00:00:00.281,000]  lorawan.lorawan_init: LoRaMAC Initialized
[00:00:00.338,000]  usb_cdc_acm: Device suspended
[00:00:00.775,000]  usb_cdc_acm: Device configured
Starting up Lora node...
Starting Lorawan stack...
Joining TTN  network over  OTTA
[00:00:02.838,000]  lorawan.lorawan_join: Network join request sent!
Sending data...
[00:00:12.162,000]  lorawan.MlmeConfirm: Received MlmeConfirm (for MlmeRequest 0)
[00:00:12.162,000]  lorawan: Joined network! DevAddr: 260XXXXX
Data sent!
[00:00:16.131,000]  lorawan.McpsIndication: Received McpsIndication 0
[00:00:16.131,000]  lorawan.McpsConfirm: Received McpsConfirm (for McpsRequest 1)
[00:00:16.131,000]  lorawan.McpsConfirm: McpsRequest success!
[00:00:26.132,000]  lorawan: LoRaWAN Send failed: Duty-cycle restricted

And that’s it. Data should be shown now at the Device Traffic tab at the TTN Applications/Device console.

Some quick notes about Lorawan LMIC Library

I’m doing some testing with https://github.com/mcci-catena/arduino-lmic with several STM32 processors (STM32F103 Bluepill, Black STM32F407VE board and the newly arrived STM32F411CE blackpill board) and the RFM95 SX1276 Lora radio for connecting to the TTN Lorawan network. For the Arduino framework the MCCI LMIC library is now the library to use, since all the other alternatives have reached end of life.

For the RFM95W radio module I’m using either the Hallard RFM95W Wemos Lora shield or the DIYcon_nl Lora shield. Please note that on this board, the thickness of the PCB is a bit greater than used by SMA edge mount pin gap intervals, so we need to bend the pins a bit to be able to solder the SMA connector.

Anyway, for the Hallard shield to work, since it “merges” all Lora DIO# pins through diodes to a single pin, a 10K pull down resistor is required to be able to work with the STM32 boards, since even with GPIO pulldown enabled I had issues with the STM32 detecting the state transition. With the resistor everything works fine. This was done since the ESP8266 Wemos board hasn’t enough pins to connect SPI + CS + DIO# pins. This doesn’t happen on the STM32 Bluepill and Blackpill and of course much less on the F407VE black board where we can use all the RFM95W/SX1276 pins.

As I said initially, this post is just some quick notes of what I’ve found when using the library with the above hardware.

EV_JOIN_TXCOMPLETE: no JoinAccept
This error is good, since it means that at hardware level everything might be 100% functional.
This issue happens when using the TTN OTAA method the APPEUI, now with the latest Lorawan releases called JOINEUI and DEVEUI are not in LSB format.
Double check the EUI format, and change the order if necessary.

Assert error at radio.c:1065
This error comes from the ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); radio.c:1065 line.
This is an hardware error. It could be a problematic RF95W module, but so far every instance of this error was caused by the following issues:

1 – Bad wiring
The most obvious issue is just bad wiring/connection, either to the SPI bus or the NSS (chip select) line. Check and double check that the SPI pins used on the STM32 are the correct ones.
For example for the BluePill and the BlackPill the SPI1 bus pins are MISO1 -> PA6, MOSI1 -> A7, SCLK1 -> A5. The NSS pin can be defined on LMIC pin definitions, which takes us to the other possible error:

2 – Bad LMIC pin definition
For the LMIC stack to work, at least the NSS, DIO0 and DIO1 pins must be defined correctly. Without NSS, the RFM95W is never select and hence will never receive the necessary commands to transmit or receive. The correct DIO# pins definition is critical since these indicate when TX and RX have ended and allow the library to retrieve or send more data.
An example that I’m using on the Blackpill F411ce board is as following:

// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = PB12,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = PA3,
    .dio = {PA0, PA1, PA2},
    //.spi_freq = 4000000
};

Your settings might not be equal, but NSS, DIO0 and DIO1 are mandatory (DIO0 = PA0, DIO1 = PA1 in my case). I have PA2 defined for DIO2, but it can be LMIC_UNUSED_PIN without any consequences.

3 – Again bad wiring
As we can see bad wiring has a lot of influence in how successful we are using the RFM95W module, but in this case even we checked and double checked that everything looks correctly defined, it still doesn’t work and gives uses the radio.c:1065 line assert error.
In all cases that I had with this error it was again bad wiring including cold solder joints.

  • Bad dupont cable
  • Colder solder joint on the NSS pin
  • Bad solder joint on the GND(!) pin

So check all the wires and solder joints for continuity, and if they look suspicious, remake them again. In one of the cases, it was the GND RFM95 board pin, that despite having a blob of solder it didn’t make a proper contact. Also look for shorts and after soldering clean any flux residues to clear out any shorts possibility.

And that’s it. I’ll probably update this post as I find issues along the way.

Setting the SPI interface for Lorawan TTN LMIC

I’m messing around with the Lorawan library LMIC, specifically the more supported MCCI LoRaWAN LMIC library that is supported on several platforms, including the STM32 platform based boards.

While the library worked out of the box on specifically the STM32F103 blue pill and the STM32F407ve black board when using SPI1 bus without any issue, a question popped up: What if I want to SPI2 for example on the STM32F407ve black board instead of the default SPI1 bus for connecting the RFM95W transceiver?

The solution is easy, and works fine if using the ST STM32 Arduino core: Before initializing the LMIC stack we set the SPI pins for the bus we want to use:

...
    // LMIC init
    SPI.setMOSI(PC3);       // Use SPI2 on the STM32F407ve board.
    SPI.setMISO(PC2);
    SPI.setSCLK(PB10);

    os_init();
    
    // Reset the MAC state. Session and pending data transfers will be discarded.
    LMIC_reset();
...
...

Note that this changes the SPI configuration used by the LMIC library which may impact other libraries that use the same SPI global variable. So we need to take caution with this possible side effect.

With this change, LMIC library works as expected by using the Lora transceiver on the SPI2 bus.

STM32 Blue Pill board, Arduino Core and USB serial output on Platformio

So I’m testing out some different language, frameworks and programming methods on STM32 using the cheap and readily available STM32F103 blue pill board.

So far I had no troubles using the USB connector as a serial device on STM32CubeIDE, CubeMX and the Zephyr RTOS.

So anyway, to keep the story (and the post short), while using the Arduino Core for STM32 and the PlatformioIO platform for doing some tests, I’ve found out that the USB Serial output didn’t worked as expected.

I did know that this issue was not with the board, since all the other frameworks/platforms had no trouble using the USB port for serial output, so I did know that the issue was due to a software and/or configuration issue.

After some searching around, the solution, while not complete was found on this Platformio forum post.

In my case I still had some errors after the original recommend configuration:

[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino
upload_protocol = stlink
build_flags =
    -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
    -D USBCON
    -D USBD_VID=0x0483
    -D USB_MANUFACTURER="Unknown"
    -D USB_PRODUCT="\"BLUEPILL_F103C8\""
Compiling .pio/build/bluepill_f103c8/FrameworkArduino/wiring_digital.c.o
/home/pcortex/.platformio/packages/framework-arduinoststm32/cores/arduino/stm32/usb/usbd_desc.c:46:4: error: #error "USB VID or PID not specified"
   46 |   #error "USB VID or PID not specified"
      |    ^~~~~
In file included from /home/pcortex/.platformio/packages/framework-arduinoststm32/system/Middlewares/ST/STM32_USB_Device_Library/Core/Inc/usbd_core.h:30,
                 from /home/pcortex/.platformio/packages/framework-arduinoststm32/cores/arduino/stm32/usb/usbd_desc.c:21:
/home/pcortex/.platformio/packages/framework-arduinoststm32/cores/arduino/stm32/usb/usbd_desc.c:160:10: error: 'USBD_PID' undeclared here (not in a function); did you mean 'USBD_VID'?
  160 |   LOBYTE(USBD_PID),           /* idProduct */
      |          ^~~~~~~~
/home/pcortex/.platformio/packages/framework-arduinoststm32/system/Middlewares/ST/STM32_USB_Device_Library/Core/Inc/usbd_def.h:275:32: note: in definition of macro 'LOBYTE'
  275 | #define LOBYTE(x)  ((uint8_t)((x) & 0x00FFU))

This was easyly solved by adding the PID (Product ID) to the configuration settings:

[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino
upload_protocol = stlink
build_flags =
    -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
    -D USBCON
    -D USBD_VID=0x0483
    -D USBD_PID=0x0100
    -D USB_MANUFACTURER="Unknown"
    -D USB_PRODUCT="\"BLUEPILL_F103C8\""

and low and behold, Serial.println() works as expected and the bluepill is detected at USB label with the VID and PID settings above defined:

Bus 001 Device 024: ID 0483:0100 STMicroelectronics STM32 STLink

 

Just a final note, all Serial.### functions could now be replaced by SerialUSB.###, for example:

SerialUSB.begin(115200);

SerialUSB.println(“Hello world\n”);

But standard Serial.## functions work now with output redirected to the USB virtual COM port.

Zephyr RTOS sample project with the STM32 blue pill board.

So this post describes more or less in detail how to build a small Zephyr RTOS project using as a target the famous and cheap STM32 blue pill board that has a ST32F103 ARM processor onboard an it is supported by Zephyr RTOS.
The project is quite simple, but it will show how to:

  1. Create a project from scratch.
  2. Create RTOS tasks
  3. Enable USB console

Creating a Zephyr RTOS project
Has documented in my previous post Zephyr RTOS – Initial setup and some tests with Platformio and the NRF52840 PCA10059 dongle and also on Zephyr documentation, we need to download, install and configure the Zephyr RTOS sources, the west tool and the supporting Zephyr SDK. This is explained on the above post.

We can create our project under the zephyr workspace directory, but then we will have trouble if we want to use git to manage our project since the zephyr workspace directory is already a git repository. So we will create our own directory outside of the zephyr workspace directory and work from there.
To do this we need to set the ZEPHYR_BASE environment variable to point to the zephyr workspace, otherwise the west tool that will compile and flash our project will fail. Since west is a python command and we are using virtual environments, as discussed on the previous post we need to first change to the virtual env that has west installed:

workon zephyr

We can now setup our project:

mkdir zSTM32usb
export ZEPHYR_BASE=/opt/Develop/zephyrproject/zephyr
cd zSTM32usb

Because I don’t want to create all the necessary files from scratch, mainly the CMakeLists.txt file, I just copy from the zephyr samples repository the simplest of the projects, blinky:

cp -R /opt/Develop/zephyrproject/zephyr/samples/basic/blinky/* .

At this point we should be able to compile and flash the STM32 blue pill board, but before that we can change the name of the project on the CMakeLists.txt file just for consistency:

# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(zstm32usb)

target_sources(app PRIVATE src/main.c)

We can now compile the project:

west build -b stm32_min_dev_blue

or if we want to do a clean build we add the -p (pristine) flag:

west build -b stm32_min_dev_blue -p

And it should compile without any issues since this is still the basic blinky project.

...
...
-- Configuring done
-- Generating done
-- Build files have been written to: /opt/Develop/zSTM32usb/build
-- west build: building application
[1/138] Preparing syscall dependency handling

[133/138] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27064 B        64 KB     41.30%
            SRAM:       12392 B        20 KB     60.51%
        IDT_LIST:         184 B         2 KB      8.98%
[138/138] Linking C executable zephyr/zephyr.elf

If we didn’t set correctly the ZEPHYR_BASE environment variable, we will get some errors. For example for listing out the available target boards, we can do a west boards command:

west boards
usage: west [-h] [-z ZEPHYR_BASE] [-v] [-V]  ...
west: error: argument : invalid choice: 'boards' (choose from 'init', 'update', 'list', 'manifest', 'diff', 'status', 'forall', 'help', 'config', 'topdir', 'selfupdate')

With the variable ZEPHYR_BASE (and virtual environment) correctly set, we get:

west boards | grep stm32
...
...
stm32373c_eval
stm32_min_dev_black
stm32_min_dev_blue
stm32f030_demo
...
...

So make sure the environment is correctly set.

Flashing the board:
Flashing the board is as easy as doing:

west flash

To be able to do this is necessary to have a ST-Link programmer and that it is properly connected to the STM32 blue pill board. Any issues here are probably not related with Zephyr or the west tool, since west only calls openocd to flash the board.

 west flash
-- west flash: rebuilding
[0/1] cd /opt/Develop/zSTM32USB/build/zephyr/cmake/flash && /usr/bin/cmake -E echo

-- west flash: using runner openocd
-- runners.openocd: Flashing file: /opt/Develop/zSTM32USB/build/zephyr/zephyr.hex
Open On-Chip Debugger 0.10.0+dev-01341-g580d06d9d-dirty (2020-05-16-15:41)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : clock speed 1000 kHz
Info : STLINK V2J36S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.212648
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32f1x.cpu       hla_target little stm32f1x.cpu       running

target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08002378 msp: 0x20002768
Info : device id = 0x20036410
Info : flash size = 64kbytes
auto erase enabled
wrote 27648 bytes from file /opt/Develop/zSTM32USB/build/zephyr/zephyr.hex in 1.769166s (15.261 KiB/s)

target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08002378 msp: 0x20002768
verified 27064 bytes in 0.404006s (65.419 KiB/s)

shutdown command invoked

And now we should have a blinking led.

Creating a project from scratch – Conclusion:
So we now have a project base that we can use that it’s outside of the zephyr workspace directory, and hence, can have it’s own git repository without clashing with the zephyr workspace repository.

We can now move to add functionality to our project.

Creating the RTOS tasks
The basic blinky sample program uses a simple main() entry point and does not create any tasks so it is as simple as it can get.

A single threaded program, like it is blinky, has a main function, and it might have other tasks, either created dynamically or statically.

In our example, we will create the tasks statically. As we can see in the main.c file for our example at the zSTM32usb Github repository we define tasks by using the predefined macro K_THREAD_DEFINE:

// Task for handling blinking led.
K_THREAD_DEFINE(blink0_id, STACKSIZE, blink0, NULL, NULL, NULL, PRIORITY, 0, 0);    

// Task to initialize the USB CDC ACM virtual COM port used for outputing data.
// It's a separated task since if nothing is connected to the USB port the task will hang...
K_THREAD_DEFINE(console_id, STACKSIZE, usb_console_init, NULL, NULL, NULL, PRIORITY, 0, 0);

According to K_THREAD_DEFINE Zephyr documentation the parameters are as follows:

K_THREAD_DEFINE(name, stack_size, entry, p1, p2, p3, prio, options, delay)
Parameters
        name: Name of the thread.
        stack_size: Stack size in bytes.
        entry: Thread entry function.
        p1: 1st entry point parameter.
        p2: 2nd entry point parameter.
        p3: 3rd entry point parameter.
        prio: Thread priority.
        options: Thread options.
        delay: Scheduling delay (in milliseconds), or K_NO_WAIT (for no delay).

Based on this, we can then fine tune the task parameters, for example the stack size that is globally defined as 1024 bytes (way too much), and produces an image that takes around 12K of SRAM:

[133/138] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27064 B        64 KB     41.30%
            SRAM:       12392 B        20 KB     60.51%
        IDT_LIST:         184 B         2 KB      8.98%
[138/138] Linking C executable zephyr/zephyr.elf

where if we cut the stacksize to 512 bytes, if frees up SRAM, which is now arounf 11K:

[133/138] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27064 B        64 KB     41.30%
            SRAM:       11368 B        20 KB     55.51%
        IDT_LIST:         184 B         2 KB      8.98%
[138/138] Linking C executable zephyr/zephyr.elf

So while in this example the stack size is equal to both tasks, in a reality each task should have it’s stack adjusted to make the most of the available SRAM/RAM.

Also since the tasks are cooperative they need to release the processor to other tasks so they can run, hence instructions that wait for resources, or just a simple sleep are required to let all tasks to run cooperatively.

In our example this is achieved by the sleep instruction k_msleep that sleeps the tasks for the miliseconds that is passed as the parameter.

For example for blinking the Led, we have:

    // Blink for ever.
    while (1) {
	gpio_pin_set(gpio_dev, led->gpio_pin, (int)led_is_on);
	led_is_on = !led_is_on;
	k_msleep(SLEEP_TIME_MS);    // We should sleep, otherwise the task won't release the cpu for other tasks!
    }

Tasks description:
Not too much to say about them, except if they do not enter a infinite loop, like the above led blinking while loop, the task does what it has to do and it ends.

Specifically for our example the led blinking task uses the Zephyr Device Tree to retrieve the onboard led configuration, and then, with that configuration it can start blinking the led. This opens the possibility of handling multiple blinking leds with the same code, just by creating a new task for each led.

The usb console init task, initiates the USB console port and waits for a port connection, after the connection happens, it starts printing to the console using the printk function. If we connect to the USB port of the STM32 blue pill board we get:

miniterm2.py /dev/ttyACM0
Hello from STM32 CDC Virtual COM port!

Hello from STM32 CDC Virtual COM port!

....

By experience console output that isn’t used for debugging purposes and/or while in development should be centralized on a single task because: first it will avoid concurrency issues between multiple tasks, and second if nothing is connected to the USB port to consume the console data, the tasks won’t hang waiting for a USB terminal connection to consume the console output.

USB Console Output configuration:
The end this already long post, we need to configure the console output to goto the USB virtual com port. This USB com port is only used for the console output, not for bidirectional communication such as an user and the device using a terminal program.

The configuration is done on the Zephyr configuration project file prj.conf, and the necessary information to enable USB console output was gathered from a series of different sources….

CONFIG_GPIO=y
CONFIG_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr Console"
CONFIG_USB_UART_CONSOLE=y

CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_UART_CONSOLE_ON_DEV_NAME="CDC_ACM_0"

A simple and quick description to the above file is that this file enables a set of modules, and provides some configuration to those modules to be able to use them. An example, to use the Led, the Led is connected to a GPIO pin, so it is necessary to enable the GPIO module: CONFIG_GPIO=y.
The same is true to enable USB. It’s necessary to enable USB support and the USB stack. Some console configuration is needed such as the CONFIG_USB_UART_CONSOLE=y, since the original, it seems, console output is to an UART port.

We can see the USB port connected when reseting the board after flashing:

[30958.705584] usb 1-1.2: new full-speed USB device number 7 using xhci_hcd
[30958.818608] usb 1-1.2: New USB device found, idVendor=2fe3, idProduct=0100, bcdDevice= 2.04
[30958.818610] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[30958.818611] usb 1-1.2: Product: Zephyr Console
[30958.818611] usb 1-1.2: Manufacturer: ZEPHYR
[30958.818612] usb 1-1.2: SerialNumber: 8701241654528651
[30958.880665] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device

and on the device list:

....
Bus 001 Device 003: ID 0483:3748 STMicroelectronics ST-LINK/V2
Bus 001 Device 007: ID 2fe3:0100 NordicSemiconductor STM32 STLink
...

where the first entry is indeed the ST-Link programmer, and the the second entry is our USB console port.

As a final note, during compilation, a warning about the device id 2fe3:0100 is given, since for production use we need to change the default:

CMake Warning at /opt/Develop/zephyrproject/zephyr/subsys/usb/CMakeLists.txt:22 (message):
  CONFIG_USB_DEVICE_VID has default value 0x2FE3.

  This value is only for testing and MUST be configured for USB products.

CMake Warning at /opt/Develop/zephyrproject/zephyr/subsys/usb/CMakeLists.txt:28 (message):
  CONFIG_USB_DEVICE_PID has default value 0x100.

  This value is only for testing and MUST be configured for USB products.

As usual we can change this by changing the VID on the prj.conf file.

CONFIG_USB_DEVICE_VID=4440

Conclusion:
And that’s it. We now have a minimal skeleton where we can start build some applications and have some console output either for tracing or general information.
The neat part is while I’ve tested this with a STM32 Blue pill board, the same code works without any modification on other boards such as the NRF52840 dongle, which shows that with the same code base we can target different boards.

STM CubeMX and multiple definition errors

So I’m using the latest STM CubeMX program version 4.23.0 to bootstrap the code of a STM32F103 based blue pill board, using Makefiles as the Toolchain/IDE.

After generating the project and executing the make command at the project root directory, I have the following errors:

build/main.o: In function `_Error_Handler':
/opt/ARM/Projects/TTNBluePill/Src/main.c:259: multiple definition of `_Error_Handler'
build/main.o:/opt/ARM/Projects/TTNBluePill/Src/main.c:259: first defined here
build/main.o: In function `MX_GPIO_Init':
/opt/ARM/Projects/TTNBluePill/Src/main.c:240: multiple definition of `SystemClock_Config'
build/main.o:/opt/ARM/Projects/TTNBluePill/Src/main.c:240: first defined here
build/main.o: In function `MX_GPIO_Init':
/opt/ARM/Projects/TTNBluePill/Src/main.c:240: multiple definition of `main'
build/main.o:/opt/ARM/Projects/TTNBluePill/Src/main.c:240: first defined here
build/stm32f1xx_it.o: In function `NMI_Handler':
/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: multiple definition of `NMI_Handler'
build/stm32f1xx_it.o:/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: first defined here
build/stm32f1xx_it.o: In function `NMI_Handler':
/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: multiple definition of `HardFault_Handler'
build/stm32f1xx_it.o:/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: first defined here
build/stm32f1xx_it.o: In function `NMI_Handler':
/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: multiple definition of `MemManage_Handler'
build/stm32f1xx_it.o:/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: first defined here
build/stm32f1xx_it.o: In function `NMI_Handler':
/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: multiple definition of `BusFault_Handler'
build/stm32f1xx_it.o:/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: first defined here
build/stm32f1xx_it.o: In function `NMI_Handler':
/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: multiple definition of `UsageFault_Handler'
build/stm32f1xx_it.o:/opt/ARM/Projects/TTNBluePill/Src/stm32f1xx_it.c:52: first defined here
build/stm32f1xx_it.o: In function `NMI_Handler':
....
....
....

And the code linkage fails.

The issue is that the generated CubeMX Makefile has duplicated entries:

######################################
# source
######################################
# C sources
C_SOURCES =  \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_uart.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_spi.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c \

Src/main.c \

Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim.c \
Src/system_stm32f1xx.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_spi_ex.c \

Src/stm32f1xx_it.c \
Src/stm32f1xx_hal_msp.c \

Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio_ex.c \

Src/main.c \
Src/stm32f1xx_it.c \

Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc.c \

Src/stm32f1xx_hal_msp.c \

Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_flash.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_i2c.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_pwr.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_flash_ex.c  

We can see that main.c, stm32f1xx_hal_msp.c and stm32f1xx_it.c are included twice on the C_SOURCES entry. For sure a CubeMX bug.

Anyway, just delete the duplicated entries, and now the compilation and linkage succeeds.

Happy coding!

Building a Blackmagic Debug Probe

The Black Magic debug probe is in-application debugger, which allows IDE’s like NetBeans, Eclipse or others to connect directly to the hardware without using bridges like for example Openocd. The Blackmagic Probe software is open source and available on this Github link.

There are several posts/instructions on the internet regarding how to load the Black Magic firmware either on ST-Link hardware debuggers, ST-Link clones or using the “blue pill” STM32F103C8T6 based board.

There are several possible issues with this board:

  1. First they are quite cheap – not an issue
  2. It seems on some versions there is a problem with the USB connector regarding a resistor and bad contacts. In my version I had no issues. It just worked fine.
  3. The on board chip is the STM32F103C8T6 which has 64Kb of flash, but some claim that it can have 128Kb. In my case all the boards that I have, the flash size is 64Kb.

This last issue is a show stopper at the current release of the Black Magic firmware since it won’t fit on the 64Kb flash of the ST32F103C8T6. We must cut things from the firmware to be able to flash it on the STM32F103C8T6 board. But we will see how to do that.

Downloading and compiling the firmware:
This step is just straight forward, just clone the Blacmagic repository and compile. Since we are compiling to ST32F103 board we will assume it is a ST-Link clone.

[pcortex@pcortex:opt]$ git clone https://github.com/blacksphere/blackmagic
Cloning into 'blackmagic'...
remote: Counting objects: 5029, done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 5029 (delta 1), reused 2 (delta 0), pack-reused 5020
Receiving objects: 100% (5029/5029), 2.01 MiB | 1.38 MiB/s, done.
Resolving deltas: 100% (3558/3558), done.
[pcortex@pcortex:opt]$ cd blackmagic/
README.md  Makefile  libopencm3/  HACKING  driver/  COPYING  scripts/  src/  upgrade/
[pcortex@pcortex:blackmagic|master]$ make
Initialising git submodules...
Submodule 'libopencm3' (https://github.com/libopencm3/libopencm3.git) registered for path 'libopencm3'
Cloning into '/opt/lixo/blackmagic/libopencm3'...
Submodule path 'libopencm3': checked out '67242de60dec0227739cd549e8a78e1a3c15dbf5'
  GENHDR  include/libopencm3/efm32/efm32gg/irq.json
  GENHDR  include/libopencm3/efm32/efm32g/irq.json
  GENHDR  include/libopencm3/efm32/efm32lg/irq.json
  GENHDR  include/libopencm3/efm32/efm32tg/irq.json
  GENHDR  include/libopencm3/stm32/f2/irq.json
...
...
...

Now we must target the firmware to our blue pill board, and so compiling the firmware as it was for a ST-Link clone:

[pcortex@pcortex:blackmagic|master]$ cd src
[pcortex@pcortex:src|master]$ make clean
 CLEAN
[pcortex@pcortex:src|master]$ make PROBE_HOST=stlink
GIT     include/version.h
  CC      target/adiv5.c
  CC      target/adiv5_jtagdp.c
  CC      target/adiv5_swdp.c
  CC      command.c
  CC      target/cortexa.c
  CC      target/cortexm.c
...
...
...
  CC      platforms/stlink/dfu_upgrade.c
  LD      dfu_upgrade
  OBJCOPY dfu_upgrade.bin
  OBJCOPY dfu_upgrade.hex

Done! On the src there is now at least two files:

  1. blackmagic_dfu.bin – The Device Firmware Upgrade loader.
  2. blackmagic.bin – The Blackmagic firmware.

Checking the file sizes we can see that the blackmagic.bin file (as of today) 57K, and the bootloader is 6.8K.

Flashing the firmware:
Flashing the firmware can be done by several forms:

  1. Using the STM32 embedded loader through a serial port
  2. Using the SWD connection, ST-Link probe and Openocd
  3. Using the SWD connection, ST-Link probe and Texane/st-link/st-util
  4. Using the DFU protocol, but it depends if the board already has a DFU firmware loaded.

Most of the posts on the internet regarding this step use the first aproach, namely using the STM32 boot loader and serial port. This needs a serial to USB converter to be connected and the STM32 boot pins to be modified.

Anyway, I’m flashing the firmware using Openocd and/or st-link utils through a ST-Link debug probe:

The blue pill pinout is as follows where the SWD connector is oposite the USB port:

From the ST-Link probe we need to connect the following pins:

  • Probe SWCLK Blue Pill DCLK
  • Probe SWDIO Blue Pill DIO
  • Probe GND Blue Pill GND

Regarding the power, I advise not to connect it and power up the blue pill board through the USB connector. This will allow to have access to both boards through USB without any possible power clashes.

We can now flash the firmware either with Texane St-Link programs or the AUR Arch Linux package stlink-git or using Openocd.

For using Openocd, create the following file and name it for example bluepill.cfg:

set CHIPNAME STM32F103C8T6

source [find interface/stlink-v2.cfg]
transport select hla_swd
source [find target/stm32f1x.cfg]
set WORKAREASIZE 0x2000

Also make sure that the ST-Link probe is detected so both st-link utils and openocd can work:

[pcortex@pcortex:src|master]$ lsusb
Bus 002 Device 003: ID 05e3:0608 Genesys Logic, Inc. Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 008 Device 003: ID 046d:c52f Logitech, Inc. Unifying Receiver
Bus 008 Device 002: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
...
Bus 003 Device 008: ID 0483:3748 STMicroelectronics ST-LINK/V2

We can now flash our board with the following command using Openocd:

openocd -f ../bluepill.cfg -c ‘init_reset halt; program blackmagic_dfu.bin 0x8000000 verify; reset;exit’

[pcortex@pcortex:src|master]$ openocd -f ../bluepill.cfg -c 'init_reset halt; program blackmagic_dfu.bin 0x8000000 verify; reset;exit'
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
0x2000
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.208372
Info : STM32F103C8T6.cpu: hardware has 6 breakpoints, 4 watchpoints
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080017b4 msp: 0x20005000
** Programming Started **
auto erase enabled
Info : device id = 0x20036410
Info : flash size = 64kbytes
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20005000
wrote 7168 bytes from file blackmagic_dfu.bin in 0.656939s (10.655 KiB/s)
** Programming Finished **
** Verify Started **
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20005000
verified 6900 bytes in 0.187018s (36.030 KiB/s)
** Verified OK **
[pcortex@pcortex:src|master]$

Using the st-flash command as documented on STM as BMP

[pcortex@pcortex:src|master]$ st-flash write blackmagic_dfu.bin 0x8000000
st-flash 1.3.1
2017-06-13T20:26:01 INFO src/common.c: Loading device parameters....
2017-06-13T20:26:01 INFO src/common.c: Device connected is: F1 Medium-density device, id 0x20036410
2017-06-13T20:26:01 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2017-06-13T20:26:01 INFO src/common.c: Attempting to write 6888 (0x1ae8) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08001800 erased
2017-06-13T20:26:01 INFO src/common.c: Finished erasing 7 pages of 1024 (0x400) bytes
2017-06-13T20:26:01 INFO src/common.c: Starting Flash write for VL/F0/F3 core id
2017-06-13T20:26:01 INFO src/flash_loader.c: Successfully loaded flash loader in sram
  6/6 pages written
2017-06-13T20:26:02 INFO src/common.c: Starting verification of write complete
2017-06-13T20:26:02 INFO src/common.c: Flash written and verified! jolly good!
[pcortex@pcortex:src|master]$

So far so good, but the above is just DFU firmware that allows to use DFU utils later to upgrade the firmware through the USB port without using any other tools.

If unplug and plug our blue pill board and check for the USB ports we have now:

[pcortex@pcortex:src|master]$ lsusb
Bus 002 Device 003: ID 05e3:0608 Genesys Logic, Inc. Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 008 Device 003: ID 046d:c52f Logitech, Inc. Unifying Receiver
Bus 008 Device 002: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
...
Bus 003 Device 012: ID 1d50:6017 OpenMoko, Inc. Black Magic Debug Probe (DFU)
Bus 003 Device 008: ID 0483:3748 STMicroelectronics ST-LINK/V2
...

And we can now also use the dfu-util command to upload the firmware.

To upload the firmware we will see that it fails due to the firmware size and the 64Kb flash size. It might not happen to you if your board has more than 64Kb of flash available.

With Openocd command:

openocd -f ../bluepill.cfg -c 'init_reset halt; program blackmagic.bin verify; reset;exit'    
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
0x2000
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.208372
Info : STM32F103C8T6.cpu: hardware has 6 breakpoints, 4 watchpoints
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080017b4 msp: 0x20005000
** Programming Started **
auto erase enabled
Info : device id = 0x20036410
Info : flash size = 64kbytes
Warn : no flash bank found for address 0
wrote 0 bytes from file blackmagic.bin in 0.016043s (0.000 KiB/s)
** Programming Finished **
** Verify Started **
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20005000
Error: checksum mismatch - attempting binary compare
diff 0 address 0x08010000. Was 0xff instead of 0x00
diff 1 address 0x08010001. Was 0xff instead of 0x00
diff 2 address 0x08010002. Was 0xff instead of 0x00
diff 3 address 0x08010003. Was 0xff instead of 0x00
diff 4 address 0x08010004. Was 0xff instead of 0x00
diff 5 address 0x08010005. Was 0xff instead of 0x00
diff 6 address 0x08010006. Was 0xff instead of 0x00

....
diff 123 address 0x0801007b. Was 0xff instead of 0x00
diff 124 address 0x0801007c. Was 0xff instead of 0x00
diff 125 address 0x0801007d. Was 0xff instead of 0x00
diff 126 address 0x0801007e. Was 0xff instead of 0x00
diff 127 address 0x0801007f. Was 0xff instead of 0x00
More than 128 errors, the rest are not printed.
embedded:startup.tcl:476: Error: ** Verify Failed **
in procedure 'program' 
in procedure 'program_error' called at file "embedded:startup.tcl", line 520
at file "embedded:startup.tcl", line 476

With the st-flash command:

[pcortex@pcortex:src|master]$ st-flash --reset write blackmagic.bin 0x8002000
st-flash 1.3.1
2017-06-13T20:37:27 INFO src/common.c: Loading device parameters....
2017-06-13T20:37:27 INFO src/common.c: Device connected is: F1 Medium-density device, id 0x20036410
2017-06-13T20:37:27 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2017-06-13T20:37:27 INFO src/common.c: Attempting to write 58084 (0xe2e4) bytes to stm32 address: 134225920 (0x8002000)
2017-06-13T20:37:27 ERROR src/common.c: addr too high
stlink_fwrite_flash() == -1
[pcortex@pcortex:src|master]$ 

And with the dfu util forcing the write above 64Kb:

[pcortex@pcortex:src|master]$ sudo dfu-util -a 0 -s 0x08002000:leave:force -D blackmagic.bin
dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

dfu-util: Invalid DFU suffix signature
dfu-util: A valid DFU suffix will be required in a future dfu-util release!!!
Opening DFU capable USB device...
ID 1d50:6017
Run-time device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 011a
Device returned transfer size 1024
DfuSe interface name: "Internal Flash   "
Downloading to address = 0x08002000, size = 58084
dfu-util: Last page at 0x080102e3 is not writeable
[pcortex@pcortex:src|master]$ 

So no luck with my board version, and as a solution we need to strip down the firmware.

Striping down the firmware:
To make the BMP firmware able to fit the 64Kb flash we have to cut things. In my case I’ve removed some processors support since my work is with the STM32 and nRF families:

Edit the file named cortexm.c located at target subdirectory, and delete the following lines around line 254 (or search by PROBE) to remove the unneeded support:

#define PROBE(x) \
        do { if ((x)(t)) return true; else target_check_error(t); } while (0)

        PROBE(stm32f1_probe);
        PROBE(stm32f4_probe);
        PROBE(stm32l0_probe);   /* STM32L0xx & STM32L1xx */
        PROBE(stm32l4_probe);
        PROBE(lpc11xx_probe);
        PROBE(lpc15xx_probe);
        PROBE(lpc43xx_probe);
        PROBE(sam3x_probe);
        PROBE(sam4l_probe);
        PROBE(nrf51_probe);
        PROBE(samd_probe);
        PROBE(lmi_probe);
        PROBE(kinetis_probe);
        PROBE(efm32_probe);

Still at the source directory we build again the firmware:

[pcortex@pcortex:src|master]$ make clean
[pcortex@pcortex:src|master]$ make PROBE_HOST=stlink
[pcortex@pcortex:src|master]$ ls -l blackmagic*
-rwxr-xr-x 1 pcortex users 753K jun 13 20:53 blackmagic*
-rwxr-xr-x 1 pcortex users  55K jun 13 20:53 blackmagic.bin*
-rwxr-xr-x 1 pcortex users 325K jun 13 20:53 blackmagic_dfu*
-rwxr-xr-x 1 pcortex users 6,8K jun 13 20:53 blackmagic_dfu.bin*
-rw-r--r-- 1 pcortex users  20K jun 13 20:53 blackmagic_dfu.hex
[pcortex@pcortex:src|master]$

We can seen now that the firmware was “slim down” around 2K, which is enough now to fit the 64Kb flash:

Using Openocd:

[pcortex@pcortex:src|master]$ openocd -f ../bluepill.cfg -c 'init_reset halt; program blackmagic.bin 0x8002000 verify; reset;exit'
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
0x2000
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.208372
Info : STM32F103C8T6.cpu: hardware has 6 breakpoints, 4 watchpoints
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080017b4 msp: 0x20005000
** Programming Started **
auto erase enabled
Info : device id = 0x20036410
Info : flash size = 64kbytes
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20005000
wrote 54272 bytes from file blackmagic.bin in 3.792976s (13.973 KiB/s)
** Programming Finished **
** Verify Started **
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20005000
verified 53744 bytes in 0.854957s (61.388 KiB/s)
** Verified OK **

Using st-flash:

[pcortex@pcortex:src|master]$ st-flash --reset write blackmagic.bin 0x8002000
st-flash 1.3.1
2017-06-13T20:58:02 INFO src/common.c: Loading device parameters....
2017-06-13T20:58:02 INFO src/common.c: Device connected is: F1 Medium-density device, id 0x20036410
2017-06-13T20:58:02 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2017-06-13T20:58:02 INFO src/common.c: Attempting to write 55720 (0xd9a8) bytes to stm32 address: 134225920 (0x8002000)
Flash page at addr: 0x0800f800 erased
2017-06-13T20:58:05 INFO src/common.c: Finished erasing 55 pages of 1024 (0x400) bytes
2017-06-13T20:58:05 INFO src/common.c: Starting Flash write for VL/F0/F3 core id
2017-06-13T20:58:05 INFO src/flash_loader.c: Successfully loaded flash loader in sram
 54/54 pages written
2017-06-13T20:58:09 INFO src/common.c: Starting verification of write complete
2017-06-13T20:58:09 INFO src/common.c: Flash written and verified! jolly good!

Using the DFU firmware upload:

[pcortex@pcortex:src|master]$ sudo dfu-util -a 0 -s 0x08002000:leave:force -D blackmagic.bin
dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

dfu-util: Invalid DFU suffix signature
dfu-util: A valid DFU suffix will be required in a future dfu-util release!!!
Opening DFU capable USB device...
ID 1d50:6017
Run-time device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 011a
Device returned transfer size 1024
DfuSe interface name: "Internal Flash   "
Downloading to address = 0x08002000, size = 55720
Download        [=========================] 100%        55720 bytes
Download done.
File downloaded successfully
Transitioning to dfuMANIFEST state

And now if we unplug the blue pill board and connect it again we should have two new tty USB based ports:

[35014.003313] usb 3-2.4: new full-speed USB device number 20 using uhci_hcd
[35019.346373] cdc_acm 3-2.4:1.0: ttyACM0: USB ACM device
[35019.349364] cdc_acm 3-2.4:1.2: ttyACM1: USB ACM device

Success!

Starting using the BMP probe:

To start using our blue pill board as a Blackmagic probe we need the following information and configuration:

  1. Connect a LED (with a resistor…) to pin PA8 to show the probe activity.
  2. The target SWD pins are CLK at pin PA5 and DIO at PB14.
  3. The support for the USB to UART bridge (the second ttyACM1 port) is PA3 – RX and PA2 – TX

So we now can use the BM probe instead of a ST-Link probe, and connect the target board SWD pins to PA5 e PB14: CLK and DIO respectively.

The led will blink when the program is running or it will fast blink when loading a program into the target board.

More information on this video from no other than the builder of the Magic Probe to see more of the probe capabilities. I also recommend the Blackmagic probe wiki for further information.

A quick example with an nRF52 based board connected to the BMP by using the ARM GDB debugger arm-none-eabi-gdb directly:

(gdb) target extended-remote /dev/ttyACM0
Remote debugging using /dev/ttyACM0
(gdb) monitor swdp_scan
Target voltage: unknown
Available Targets:
No. Att Driver
 1      Nordic nRF52
(gdb) att 1
(gdb) monitor
Black Magic Probe (Firmware v1.6.1-25-gaaa7b0e-dirty) (Hardware Version 0)
Copyright (C) 2015  Black Sphere Technologies Ltd.
License GPLv3+: GNU GPL version 3 or later 


Interesting links:

Most of this post wouldn’t be possible without gathering information from around the web. Here is a set of information that might be useful to gather more or missing information from this post:

  1. Black Magic Probe (BMP) on ST-Link v2 clones
  2. STM Discovery as Black Magic Probe
  3. STM32Duino forum