STM32F1xx micro-controller C firmware template
Go to file
King Kévin 2e917bed9e README: minor, fix typo 2021-05-10 12:41:45 +02:00
lib usart_enhanced: mark STM32F4 compatible (no difference with STM32F1) 2021-05-08 10:25:21 +02:00
libopencm3@2b603ff2af libopencm3: use branch with OTG fix 2020-12-11 00:00:25 +01:00
.gitignore cherry-pick from busvoodoo branch, part 2 2018-02-18 15:20:01 +01:00
.gitmodules libopencm3: use branch with OTG fix 2020-12-11 00:00:25 +01:00
Doxyfile doc: fix documentation 2020-01-03 00:16:59 +01:00
LICENSE.txt add GPLv3 license file 2018-02-13 15:14:10 +01:00 README: minor, fix typo 2021-05-10 12:41:45 +02:00
Rakefile Rakefile: ingnore .inc files 2021-03-23 17:22:48 +01:00
application.c application: minor, rename autodetect to tx 2021-05-10 12:34:30 +02:00
application.ld *.ld: set flash and RAM size for STM32F401xC 2020-12-11 00:00:25 +01:00
bootloader.c bootloader: update to work with F4 2020-12-11 00:00:25 +01:00
bootloader.ld *.ld: set flash and RAM size for STM32F401xC 2020-12-11 00:00:25 +01:00
global.c application: remove button since we will use the pins 2021-03-24 00:20:11 +01:00
global.h global: disable DEBUG 2021-03-24 17:06:12 +01:00

firmware is for the I/O finder.



this tools allow to identify if channels (e.g. lines) are inputs or outputs. up to 16 channels can be identified at once. it also allows to monitor the channel activity, identify UART TX and RX signals.


connect the pins or test points of the target device to the I/O finder channels pins on the connector. select the first and last channel to probe using the start <CH> ans stop <CH> commands. this prevents false activity monitoring alerts. it will also make the scan faster.

also connect the target voltage pin to the SWJ finder in order to use the right signal voltage level. alternatively, the SWJ finder can supply 3.3V to the target voltage pin using the voltage 3 command. to revert to using the externally provided target voltage, use the voltage 0 command. to measure the target voltage, use the voltage command.

to find out what type of I/O is connected to the channel, use the type command. it will measure the voltage while applying a pull-up and pull-down. if the voltage changes, the channel is likely an input (or floating). it will also show what value of pull-up/down is already on the channel. if the voltage does not change, the channel is likely an output (or GND/power supply rail). be aware that this is just the result at the testing type, as the target can change its configuration.

to monitor the activity of the channels, use the monitor command. provide no argument to not pull the channels. provide the 0 argument to pull the channels low using the internal 40 kOhm resistor. provide the 3 argument to pull the channels high to 3.3V using the internal 40 kOhm resistor. pulling the channels may reduce the noise, in case the channel is floating. the activity and current state (with time stamp) of the pin will be displayed (up to 0.1s speed). if the channel is switch fast, an 'X' will appear in from of the current level. to stop monitoring, press any key.

to monitor the activity of a single channel, use the monitor_single command. provide as argument the channel you want to monitor. this works like the monitor command, but has the advantage to detect high voltages down to 1.5V (ideal for 1.8V logic). it will also show the (maximum) frequency of the signal (useful to clock and baud rate measurement).

to detect the configuration of a UART stream, use the uart_tx command. provide as argument the channel you want to monitor. as data comes it, it will figure out the baud rate, data bits, and parity of the stream. the configuration is displayed as it finds a better match. it will also show the decoded data. this mainly works for ASCII based communication (e.g. human readable debug logs), and might not give correct results for binary communication.

to find the corresponding UART RX pin (e.g. once UART TX pin has been identified using the uart_tx command), use the uart_rx commands. provide as argument the channel for the UART TX pin, and the UART baud rate and frame settings (e.g. 115200 8N1). data will be transmitted on the channels to probe and if we received the echo back on the TX, we found the RX. this detection method only works if the target echoes back the data (e.g. on login prompts).

you can also reset the target board if you connected to target reset pin to the SWJ finder. you can select of to drive the reset pin (OD for open-drain, PP for push-pull) and active level (H for high, L for low) using the reset [ODL|ODH|PPL|PPH] command. to assert or release the reset, us the reset 1 or reset 0 commands. alternatively, pressing/releasing the button on the SWJ finder asserts/releases the reset signal

use the help command to list all commands. this will also list the shortcuts for the commands.


to figure out the type of I/O a channel is, it:

  • selects the channel on a analog multiplexer
  • measures the voltage while applying a 2 kOhm pull-down (to ground)
  • measures the voltage while applying a 2 kOhm pull-up (to target voltage)
  • calculate the resistance on the channel (since it known the resistor values of the voltage divided used to measure the channel voltage)

to monitor the activity, it directly reads the level of all 16 channels in parallel. no voltage shifter is used (the input have a high impedance to not affect the channels). any voltage above 3.3*0.7=2.3V is detected as high, else it is low.


the target voltage should not be higher than 5.5V (board I/O-pins limitation). the channel voltage level need to be above 2.3V for activity to be detected reliably when multiple channels are monitored. the channel voltage level need to be above 1.5V for activity to be detected reliably when a single channel is monitored.

the reset pin has no inline protection resistor and can sink up to 25 mA. in open drain mode, an external pull-up resistor is required, most often provide by the target device. in push-pull mode it can only source 3.3V up to 25 mA.


the underlying hardware uses a WeAct MiniF4 board, based on a STM32F401CCU6. the bi-directional level shifter are BSS138 n-channel MOSFET based. switching the target voltage are done using BSS84 p-channel MOSFET.


remove LED on C13 on MiniF4 board so we can reuse the pin.

IDC 2x10 connector:

  1. GND, connect to ground
  2. VTRG, target voltage, see below for connection
  3. RST, to reset the target board, connect to PA0
  4. 3V3, connected to 3.3V
  5. CH00, connect to PB10
  6. CH01, connect to PB9
  7. CH02, connect to PB8
  8. CH03, connect to PB7
  9. CH04, connect to PB6
  10. CH05, connect to PB5
  11. CH06, connect to PB4
  12. CH07, connect to PB3
  13. CH08, connect to PA15
  14. CH09, connect to PA10
  15. CH10, connect to PA9
  16. CH11, connect to PA8
  17. CH12, connect to PB15
  18. CH13, connect to PB14
  19. CH14, connect to PB13
  20. CH15, connect to PB12

ADC, used to measure target voltage (up to 6.6V):

  • connect VTRG to 22 kOhm resistor R1
  • connect PA6 (ADC1_IN6) to other side of resistor R1
  • connect PA6 to 22 kOhm resistor R2
  • connect GND to other side of resistor R2, forming a voltage divided

analog multiplexer, 16-channel, HP4067, to probe individual channels:

  • channels: C0-C15 to CH0-CH15
  • select: S3 to PB2, S2 to PB1, S1 to PB0, S0 to PA7
  • enable: EN to PC15, pulled up to VCC
  • signal: S to see below
  • VCC: to 5V (for better switching, the select line can still be operated at 3.3V)
  • GND: to ground

ADC for signal, using BSS84 p-channel MOSFET Q5:

  • connect signal S to PA1/ADC1_IN1 through 1 KOhm resistor (first part of voltage divider)
  • connect 1 kOhm between PA1 and PA4, creating second part of voltage divider (PA4 can ground it)
  • Q5 source: to VTRG
  • Q5 gate: to PA5, pulled up to VTRG using 10-100 kOhm resistor
  • Q5 drain: to PA4 (PA5 can set connect voltage divider to high side)

UART, with level shifter, to scan for UART port:

  • short PA3 (RX) and PA2 (TX), making it a half-duplex UART (sufficient for testing most UARTs)
  • connect PA2/PA3 to 150 Ohm in-line resistor R3 (used as protection to limit sink current)
  • pull up R3 to 5V using 10 kOhm ressitor, this is necessary for the voltage shifter to operate up to 5V
  • connect R3 to source of BSS138 n-channel MOSFET Q1, used as bi-directional level shifter
  • connect gate of Q1 to Q2 drain
  • connect source of Q1 to signal S

level shifter control, using BSS84 p-channel MOSFET Q2:

  • gate: to PC14, pulled up to VTRG using 10-100 kOhm resistor
  • source: VTRG
  • drain: to signal S through 10 kOhm (this creates a pull-up resistor for the low side level shifter)

target voltage supply control, using BSS84 p-channel MOSFET Q3 (to provide 3.3V) and BSS138 n-channel MOSFET Q4 (to prevent feed back into 3.3V):

  • Q3 gate: PC13, pulled up to 3.3V using 10-100 kOhm resistor
  • Q3 source: 3.3V
  • Q3 drain: Q4 source, 3.3V output for target
  • Q4 source: Q3 drain, forward 3.3V to target (through body diode and because gate is on)
  • Q4 gate: to 5V, enabling transition until 5V from target is present on drain
  • Q4 drain: VTRG

all pins are configured using defines in the corresponding source code.



The source code uses the libopencm3 library. The projects is already a git submodules. It will be initialized when compiling the firmware. Alternatively you can run once: git submodule init and git submodule update.


To compile the firmware run rake.


To generate doxygen documentation run rake doc.


There are two firmware images: bootloader and application. The bootloader image allows to flash the application over USB using the DFU protocol. The bootloader is started first and immediately jumps to the application if it is valid and the DFU mode is not forced (i.e. by pressing the user button on the board or requesting a DFU detach in the application). The application image is the main application and is implemented in application.c. It is up to the application to advertise USB DFU support (i.e. as does the provided USB CDC ACM example).

The simplest way do flash the bootloader image is using the embedded bootloader. By pressing the BOOT0 button (setting the pin low) while powering or resetting the device, the micro-controller boot its embedded UART/USB DFU bootloader. Connect a USB cable and run rake dfu_bootloader.

Once the bootloader is flashed, it is possible to flash the application over USB using the DFU protocol by running rake flash (equivalent to rake dfu_application. To force the bootloader to start the DFU mode press the user button or short a pin, depending on the board. Note: I use my own DFU bootloader instead of the embedded bootloader because I was not able to start the embedded USB DFU bootloader from the application.

The images can also be flash using SWD (Serial Wire Debug) in case the firmware gets stuck and does not provide USB functionalities. For that you need an SWD adapter. The Makefile uses a ST-Link V2 programmer along OpenOCD software (default), or Black Magic Probe. To flash the bootloader using SWD run rake swd_bootloader (this will also erase the application). To flash the application using SWD run rake swd_application (or rake swd). To erase all memory and unlock read/write protection, run rake remove_protection.


SWD also allows to debug the code running on the micro-controller using GDB. To start the debugging session run rake debug.


The firmware offers serial communication over USB (using the CDC ACM device class).


add monitor single channel to support 1.8V voltage. measure frequency of single channel. decode single channel as RX. probe UART using TX than RX.