Turning ST-Link programmer into IR controlled USB keyboard

As I promised last time, I’m going to continue with st-link programmer clones. This time I wanted to use mbed which has some ready made libraries for utilizing USB and since it was very comfortable to use DFU upload with arduino library, at first I checked if it is usable with mbed too.

Preparing DFU bootloader

So I googled a bit and found this example. It describes how to build and install DFU bootlader. What I like about it is that it resides in the upper 8kB of flash so you don’t need to modify your main application. Because my board has very little pins broken out, I cannot afford to sacrifice one for forcing boot into DFU so I made some changes to the bootloader so it stays in DFU mode after each boot and then starts the main code – similarly like the one in maple_mini. My board has 128kB of flash so I also changed the LDSCRIPT:

diff --git a/src/targets.mk b/src/targets.mk
index 75f3b69..68962cc 100644
--- a/src/targets.mk
+++ b/src/targets.mk
@@ -36,7 +36,7 @@ endif
 ifeq ($(TARGET),STLINK)
 TARGET_COMMON_DIR := ./stm32f103
 TARGET_SPEC_DIR := ./stm32f103/stlink
- LDSCRIPT := ./stm32f103/stm32f103x8.ld
+ LDSCRIPT := ./stm32f103/stm32f103xB.ld
 ARCH = STM32F1
 endif

and uploaded it to board as last time using openocd:

> reset halt 
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0801f590 msp: 0x20005000
> stm32f1x mass_erase 0 
stm32x mass erase complete
> flash write_bank 0 dapboot/src/dapboot.bin 
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20005000
wrote 130896 bytes from file dapboot/src/dapboot.bin to flash bank 0 at offset 0x00000000 in 3.901665s (32.762 KiB/s)
>

After reconnecting, DFU device appeared:

[156988.531502] usb 2-3: new full-speed USB device number 64 using xhci_hcd
[156988.673085] usb 2-3: New USB device found, idVendor=1209, idProduct=db42
[156988.673091] usb 2-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[156988.673096] usb 2-3: Product: DAPBoot DFU Bootloader
[156988.673100] usb 2-3: Manufacturer: Devanarchy
[156988.673104] usb 2-3: SerialNumber: 553803874970525449FF6E06

Building main application

Next question was how to build working application for my board. Again I’ve found nice example on mbed pages. The key is this section:

  • Create a program as if it was for a NUCLEO-F103RB board (select NUCLEO-F103RB as target platform for the online compiler). Or click here to import this demo into your online compiler (then you can skip the following two steps).
  • Import the mbed-STM32F103C8T6 library into your project
  • Add #include "stm32f103c8t6.h" to main.cpp before #include "mbed.h" (the position matters!).

I needed to modify it because LED on my board is on different pin, so I created local copy, set LED to PA_9 and built it on my own:

$ hg clone https://danman@os.mbed.com/users/hudakz/code/STM32F103C8T6_Hello/
destination directory: STM32F103C8T6_Hello
requesting all changes
adding changesets
adding manifests
adding file changes
added 12 changesets with 21 changes to 4 files
updating to branch default
3 files updated, 0 files merged, 0 files removed, 0 files unresolvedy board has 128kB of flash so I also changed 
$ cd STM32F103C8T6_Hello/
$ pip install mbed-cli
$ vi main.cpp 
$ mbed deploy
[mbed] Adding library "mbed" from "https://mbed.org/users/mbed_official/code/mbed/builds" at rev #64910690c574
[mbed] Downloading library build "64910690c574" (might take a minute)
[mbed] Unpacking library build "64910690c574" in "/home/danman/Projects/STM32F103C8T6_Hello/mbed"
[mbed] Adding library "mbed-STM32F103C8T6" from "https://mbed.org/users/hudakz/code/mbed-STM32F103C8T6" at rev #727468adfd1d
[mbed] Couldn't find build tools in your program. Downloading the mbed 2.0 SDK tools...
[mbed] Updating the mbed 2.0 SDK tools... 
$ mbed compile -m NUCLEO_F103RB -t GCC_ARM
[mbed] WARNING: Could not find mbed program in current path "/home/danman/Projects/STM32F103C8T6_Hello".
[mbed] WARNING: You can fix this by calling "mbed new ." in the root of your program.
---
Building project STM32F103C8T6_Hello (NUCLEO_F103RB, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Compile [ 50.0%]: main.cpp
Compile [100.0%]: SysClockConf.cpp
Link: STM32F103C8T6_Hello
Elf2Bin: STM32F103C8T6_Hello
+---------------------+-------+-------+------+
| Module | .text | .data | .bss |
+---------------------+-------+-------+------+
| BUILD/NUCLEO_F103RB | 192 | 0 | 0 |
| [fill] | 42 | 0 | 6 |
| [lib]/c.a | 52481 | 2248 | 56 |
| [lib]/gcc.a | 3360 | 0 | 0 |
| [lib]/mbed.a | 3050 | 0 | 42 |
| [lib]/misc | 296 | 12 | 28 |
| mbed/64910690c574 | 7780 | 8 | 720 |
| Subtotals | 67201 | 2268 | 852 |
+---------------------+-------+-------+------+
Total Static RAM memory (data + bss): 3120 bytes
Total Flash memory (text + data): 69469 bytes

Image: ./BUILD/NUCLEO_F103RB/GCC_ARM/STM32F103C8T6_Hello.bin
$ dfu-util -D ./BUILD/NUCLEO_F103RB/GCC_ARM/STM32F103C8T6_Hello.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 1209:db42
Run-time device DFU version 0110
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 0110
Device returned transfer size 1024
Copying data from PC to DFU device
Download [=========================] 100% 69316 bytes
Download done.
state(7) = dfuMANIFEST, status(0) = No error condition is present
dfu-util: unable to read DFU status after completion
danman@silverhorse:~/Projects/STM32F103C8T6_Hello$

BLINKING LED!

USB communication

Another more interesting example is USBSerial . It creates a virtual serial port which you can read/write from you code.

$ hg clone https://danman@os.mbed.com/users/hudakz/code/STM32F103C8T6_USBSerial/
destination directory: STM32F103C8T6_USBSerial
requesting all changes
adding changesets
adding manifests
adding file changes
added 6 changesets with 14 changes to 4 files
updating to branch default
4 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd STM32F103C8T6_USBSerial/
$ mbed deploy
[mbed] Adding library "mbed" from "http://mbed.org/users/mbed_official/code/mbed/builds" at rev #97feb9bacc10
[mbed] Downloading library build "97feb9bacc10" (might take a minute)
[mbed] Unpacking library build "97feb9bacc10" in "/home/danman/Projects/STM32F103C8T6_USBSerial/mbed"
[mbed] Adding library "mbed-STM32F103C8T6" from "https://mbed.org/users/hudakz/code/mbed-STM32F103C8T6" at rev #09d8c2eacb4d
[mbed] Adding library "USBDevice_STM32F103" from "https://developer.mbed.org/users/hudakz/code/USBDevice_STM32F103" at rev #c86eab438152
[mbed] Couldn't find build tools in your program. Downloading the mbed 2.0 SDK tools...
[mbed] Updating the mbed 2.0 SDK tools... 
$ mbed compile -m NUCLEO_F103RB -t GCC_ARM
[mbed] WARNING: Could not find mbed program in current path "/home/danman/Projects/STM32F103C8T6_USBSerial".
[mbed] WARNING: You can fix this by calling "mbed new ." in the root of your program.
---
Building project STM32F103C8T6_USBSerial (NUCLEO_F103RB, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Compile [ 7.7%]: USBAudio.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
[Warning] USBAudio.h@134,30: 'void mbed::Callback<R()>::attach(R (*)()) [with R = void]' is deprecated: Replaced by simple assignment 'Callback cb = func [since mbed-os-5.4] [-Wdeprecated-declarations]
[Warning] USBAudio.cpp@25,230: 'mbed::FunctionPointerArg1<R, void>::FunctionPointerArg1(R (*)()) [with R = void]' is deprecated: FunctionPointer has been replaced by Callback<void()> [since mbed-os-5.1] [-Wdeprecated-declarations]
Compile [ 15.4%]: USBHID.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 23.1%]: USBHAL_STM32F1.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 30.8%]: USBDevice.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 38.5%]: USBKeyboard.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 46.2%]: USBMouse.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 53.8%]: USBMIDI.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 61.5%]: USBMouseKeyboard.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [ 69.2%]: USBCDC.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
[Warning] USBDescriptor.h@46,21: narrowing conversion of '(((int)((USBCDC*)this)->USBCDC::<anonymous>.USBDevice::VENDOR_ID) & 255)' from 'int' to 'uint8_t {aka unsigned char}' inside { } is ill-formed in C++11 [-Wnarrowing]
[Warning] USBDescriptor.h@47,30: narrowing conversion of '(int)(((USBCDC*)this)->USBCDC::<anonymous>.USBDevice::VENDOR_ID >> 8)' from 'int' to 'uint8_t {aka unsigned char}' inside { } is ill-formed in C++11 [-Wnarrowing]
[Warning] USBDescriptor.h@46,21: narrowing conversion of '(((int)((USBCDC*)this)->USBCDC::<anonymous>.USBDevice::PRODUCT_ID) & 255)' from 'int' to 'uint8_t {aka unsigned char}' inside { } is ill-formed in C++11 [-Wnarrowing]
[Warning] USBDescriptor.h@47,30: narrowing conversion of '(int)(((USBCDC*)this)->USBCDC::<anonymous>.USBDevice::PRODUCT_ID >> 8)' from 'int' to 'uint8_t {aka unsigned char}' inside { } is ill-formed in C++11 [-Wnarrowing]
Compile [ 76.9%]: main.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
[Warning] USBSerial.h@59,214: 'mbed::FunctionPointerArg1<R, void>::FunctionPointerArg1(R (*)()) [with R = void]' is deprecated: FunctionPointer has been replaced by Callback<void()> [since mbed-os-5.1] [-Wdeprecated-declarations]
[Warning] USBSerial.h@116,25: 'void mbed::Callback<R()>::attach(R (*)()) [with R = void]' is deprecated: Replaced by simple assignment 'Callback cb = func [since mbed-os-5.4] [-Wdeprecated-declarations]
Compile [ 84.6%]: USBSerial.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
[Warning] USBSerial.h@59,214: 'mbed::FunctionPointerArg1<R, void>::FunctionPointerArg1(R (*)()) [with R = void]' is deprecated: FunctionPointer has been replaced by Callback<void()> [since mbed-os-5.1] [-Wdeprecated-declarations]
[Warning] USBSerial.h@116,25: 'void mbed::Callback<R()>::attach(R (*)()) [with R = void]' is deprecated: Replaced by simple assignment 'Callback cb = func [since mbed-os-5.4] [-Wdeprecated-declarations]
[Warning] USBSerial.cpp@55,23: comparison between signed and unsigned integer expressions [-Wsign-compare]
[Warning] USBSerial.cpp@33,12: 'c' may be used uninitialized in this function [-Wmaybe-uninitialized]
Compile [ 92.3%]: USBMSD.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [100.0%]: SysClockConf.cpp
Link: STM32F103C8T6_USBSerial
Elf2Bin: STM32F103C8T6_USBSerial
+---------------------+-------+-------+------+
| Module | .text | .data | .bss |
+---------------------+-------+-------+------+
| BUILD/NUCLEO_F103RB | 5400 | 236 | 1494 |
| [fill] | 58 | 4 | 10 |
| [lib]/c.a | 52413 | 2248 | 56 |
| [lib]/gcc.a | 3360 | 0 | 0 |
| [lib]/mbed.a | 2507 | 0 | 25 |
| [lib]/misc | 296 | 12 | 28 |
| mbed/97feb9bacc10 | 12407 | 8 | 719 |
| Subtotals | 76441 | 2508 | 2332 |
+---------------------+-------+-------+------+
Total Static RAM memory (data + bss): 4840 bytes
Total Flash memory (text + data): 78949 bytes

Image: ./BUILD/NUCLEO_F103RB/GCC_ARM/STM32F103C8T6_USBSerial.bin
$ dfu-util -D ./BUILD/NUCLEO_F103RB/GCC_ARM/STM32F103C8T6_USBSerial.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 1209:db42
Run-time device DFU version 0110
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 0110
Device returned transfer size 1024
Copying data from PC to DFU device
Download [=========================] 100% 78796 bytes
Download done.
state(7) = dfuMANIFEST, status(0) = No error condition is present
dfu-util: unable to read DFU status after completion

And the result is…

$ cat /dev/ttyACM0 


I am a USB serial prt
I am a USB srial ort
I am a serial port
I am a USB serial po
I am a USBeri
I am SB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
I am a USB serial port
^C
$

The USB serial is a bit buggy (Update: works ok after disabling ModemManager: systemctl stop ModemManagers) but it doesn’t matter, the main point is that it works! But this also works with arduino framework so nothing that new.

Emulating USB keyboard

Next I wanted to try something different. The USBDevice lib includes functions to emulate USB keyboard. So I put together an example which blinks LED and sends CapsLock key so it also makes your keyboard blink. You can find it here and the result looks like this:

Using PlatformIO

Then I wanted to recreate the blinking keyboard using PlatformIO so I took the mbed-blink code (which worked) and replaced the main.cpp with the one from project above. At first it was not working and I opened issue on github but then I found out that the USB library is probably not correctly linked in and only needs correct configuration. I solved this by putting line in platformio.ini:

lib_deps = https://os.mbed.com/users/hudakz/code/USBDevice_STM32F103/

This way I got working binary for upload with DFU.

Automating DFU upload

Until now I was still manually uploading the compiled binary with dfu-util. But I wanted to integrate it with PlatformIO so it would be uploaded automatically. PIO allows you to add custom upload/build scripts. So I made one which modifies upload process to use my own script which in turn uses dfu-util.

The working project can be found here and the upload is super comfortable. Just press upload in PIO, reinsert USB dongle when asked and it’s done.

The result

When I had all components ready, it was time to create something useful. With just 1 additional component I decided to create IR remote control receiver. For this I used SFH-506 IR demodulator. Connection was easy: just VCC, GND and one signal pin. The pin pitch after desoldering pin header was correct but not a perfect match with receiver, so I helped it with one jumper wire:

I was quite satisfied with the resulting look. Next step was to write the software. Similarly as the arduino framework, mbed has many ready-to-use libraries and I was hoping to find one, which will process IR signals and fortunately I’ve found one. So I just took the example code, let the output print to USBSerial and watched the result after pressing keys:

Because I’ve had working config for this remote, I compared the output with key definitions and found out the pattern for decoding:

key = buf[0]*256 + buf[2];

Next I wrote simple switch-case construction mapping IR keys to keyboard commands and the result can be found in this repository and seen here:

This simple project took me about one hour but to prepare all the components took me a lot of hours of studying dfu, PIO, mbed and trial and error so I hope it helps or at least inspires someone.

I still have some ideas what to do with this USB dongle so maybe I’ll post something more in the future.

Looking forward to you comments.

Bye!

7 thoughts on “Turning ST-Link programmer into IR controlled USB keyboard”

  1. Hi Dan, I had the same idea and came across your post. I’ve been able to flash the bootloader and upload a bin file via dfu-util, but seem unable to get the LED to blink! Very basic. I’ve downloaded the image from the device and verified it was written properly, and the DFU device disappears after flashing so I assume it’s executing code.

    Any thoughts?
    Thanks.

    1. Hi Eddie. What kind of bootloader have you uploaded? Can you share the output of:
      hd [bootloader_file] | head

      1. Hi Dan, I used dapboot, pulling your changes into master and increasing the timeout since I wasn’t able to catch it in DFU mode. I don’t have the logs with me, I’ll reply tonight.

        FYI, I tried both blink program referenced above, updating led pin to PA_9 and also a libopencm3 based blink program. Very simple so surprised it didn’t work.

        Thanks.

          1. Thanks, Dan. I was on the wrong branch. After fixing that, I was able to flash a tiny flash sample written in libopencm3 and it worked! But for completeness I tried the same Hello example using mbed and the flash fails towards the end and wipes out the bootloader.

            Run-time device DFU version 0110
            Claiming USB DFU Interface…
            Setting Alternate Setting #0 …
            Determining device status: state = dfuIDLE, status = 0
            dfuIDLE, continuing
            DFU mode device DFU version 0110
            Device returned transfer size 1024
            Copying data from PC to DFU device
            Download [====================== ] 88% 56320 bytes dfu-util: Error during download get_status

            I wonder if the ST-LINK I have doesn’t actually have 128k. Thanks for your help, I’ll keep digging at it.

  2. A follow-up in case anyone makes the same mistakes I made. I had for some reason not pulled from the highboot branch, so that wasn’t going to work. And then when I switched branches I forgot to reset the target to the 128k config file, so I was flashing the bootloader to the middle of the memory range.

    In my defense I was trying this at 1am and that’s getting harder to do without copious amounts of coffee.

    Thanks again.

Leave a Reply to danman Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.