Friday, February 23, 2018

The Bluepill+

The STM32L443CC 'Bluepill+'

Following up from the work I did on getting Ada going on a Bluepill.

Is a Bluepill a fixed entity?

Looking at the SoC the Bluepill uses we see it is a Cortex-M3 running at 72Mhz with 128k of flash and 20k of ram. There are newer STM32 chips all the way out to Cortex-M7's. I have this tool from ST called the ST MCUFinder. You can screen all their parts by different parameters. I had already hacked Ada onto the STM32L432 for the Make-with-Ada contest last year so the L series was still quite fresh in my mind. So looking at the package the STM32F103C8 on a stock bluepill was there a newer, pin compatible, STM32 cpu across the family that could drop in and work in place of the original? I will spare dear reader the suspense, indeed there are a bunch of parts that have the same footprint.

The candidate CPU for Bluepill+ STM32L443CCT6

That part uses a Cortex-M4F, runs at 80Mhz with 256k of flash and 64k of ram. Plus it has more peripherals than the STM32F103C8. This 443CC has 2 12 bit DACs. I like 2 DACs since you can make oscilloscope vectors with that HW.


  • Core: ARM® 32-bit Cortex®-M4 CPU with FPU, Adaptive real-time accelerator (ART Accelerator™) allowing 0-wait-state execution from Flash memory, frequency up to 80 MHz, MPU, 100DMIPS and DSP instructions
  • Performance benchmark
    • 1.25 DMIPS/MHz (Drystone 2.1)
    • 273.55 CoreMark® (3.42 CoreMark/MHz @ 80 MHz)
  • Energy benchmark
    • 176.7 ULPBench® score
  • Clock Sources
    • 4 to 48 MHz crystal oscillator
    • 32 kHz crystal oscillator for RTC (LSE)
    • Internal 16 MHz factory-trimmed RC (±1%)
    • Internal low-power 32 kHz RC (±5%)
    • Internal multispeed 100 kHz to 48 MHz oscillator, auto-trimmed by LSE (better than ±0.25 % accuracy)
    • Internal 48 MHz with clock recovery
    • 2 PLLs for system clock, USB, audio, ADC
  • Up to 83 fast I/Os, most 5 V-tolerant
  • RTC with HW calendar, alarms and calibration
  • LCD 8× 40 or 4× 44 with step-up converter
  • Up to 21 capacitive sensing channels: support touchkey, linear and rotary touch sensors
  • 11x timers: 1x 16-bit advanced motor-control, 1x 32-bit and 2x 16-bit general purpose, 2x 16-bit basic, 2x low-power 16-bit timers (available in Stop mode), 2x watchdogs, SysTick timer
  • Memories
    • 256 KB single bank Flash, proprietary code readout protection
    • 64 KB of SRAM including 16 KB with hardware parity check
    • Quad SPI memory interface
  • Rich analog peripherals (independent supply)
    • 1× 12-bit ADC 5 Msps, up to 16-bit with hardware oversampling, 200 μA/Msps
    • 2x 12-bit DAC, low-power sample and hold
    • 1x operational amplifier with built-in PGA
    • 2x ultra-low-power comparators
  • AES: 128/256-bit key encryption hardware accelerator
  • 17x communication interfaces
    • USB 2.0 full-speed crystal less solution with LPM and BCD
    • 1x SAI (serial audio interface)
    • 3x I2C FM+(1 Mbit/s), SMBus/PMBus
    • 4x USARTs (ISO 7816, LIN, IrDA, modem)
    • 1x LPUART (Stop2 wake-up)
    • 3x SPIs (4x SPIs with the Quad SPI)
    • CAN (2.0B Active) and SDMMC interface
    • SWPMI single wire protocol master I/F
    • IRTIM (Infrared interface)
  • 14-channel DMA controller
  • True random number generator
  • CRC calculation unit, 96-bit unique ID


If you look on eBay for the price of a Bluepill, its just a tidge over $2. Last I checked, $2.13. Take a look at the 10k quantity for STM32F103C8's btw. Its at that price(!!!). So a Bluepill has: 
1) a pcb
2) STM32F103C8
3) 8Mhz xtal
4) 32khz xtal
5) 5v - 3v reg
6) 2 LEDs
7) reset button
8) USB header

All for the 10k unit quantity of 2) above...

Anyway, the donor CPU an STM32L443CCT6 is about $2.61 in 10,000 unit qty. For me buying 5 or so was about $5 to $6 each so really a Bluepill+ costs about 8$ after surgery. Even at $8 this board is pretty good for what you get.

The donor

A $2 bluepill board was sacrificed to be a donor for Bluepill+ updating. Basically, since the parts are pin compatible you need only use a cheap hot air station (these are sub $100 on eBay) and some tweezers. Get a helper to spread the hot air and gently tweeze the package away from the board as the solder liquefies. I had also tin foiled the rest of the board to avoid stray heat. Once off the board, the donor IC is placed on the footprint (carefully!) and then using an iron, tack down the legs to the liberated pads. The residual solder is enough to make good contact I found even after cleaning the pads up a little bit post removal.

Porting Ada

So, we have a new CPU. Lets get Ada going on it! We will need some low level code changes to get the board initialized to be useful. Things like the PLL has to get locked to the right clock, other L series SoC IP needs to enabled etc. Fortunately, there are ample examples in Ada_Drivers_Library on how other F4 and F7 series cores are handled. The STM32L series is modern relative to the F1 and F4 and shares some commonality with the F7. So we look closely at the F7 when in doubt about init seqs. One easy thing to get going is to grab the SVD files from ST for the L443. These are right on the SoCs ST webpage.  Once you have them, you can run svd2ada to generate the headers, one set for Ada_Drivers_Library and one for the embedded runtimes. I know that this method has be modernized but I have not yet moved over to bb_runtimes at this time. I also wrote a tool in Ruby that sucks in the SVD fie and generates a gdb script. That script is personalized for your SoC, it generates reg dump log files of the entire SoC and saves them to disk. Subsequently, another tool pulls in the svd and the logs and generates a human readable hierarchy dump of each reg. This is documented on the blog. So once the svd files are generated porting begins for the various IP blocks in the design The key ones to port are GPIO, SPI, I2C, TImers, ADC, DMA. This work took some time but I leveraged my work for Make with Ada (an STM32L432). At this point there is parity between my Bluepill port and the Bluepill+ port. All the code with examples is in my STM32 tree:

with libraries:


Also the Ruby SVD reader is here:

One technique I found helpful in my Ada port was that ST via its CubeMX and free SW dev tools has lots of examples. If you get stuck with some IP thats not working in Ada. Build a CubeMX example that initializes or uses the IP you need to port. Once thats up and running on your board, use the Ruby scripts I mentioned to dump the ST example codes reg settings. First you can use that as a reference, and later use logs2dump and diff your port to the example code. Where there are diffs might expain why your port is not working.


Openocd 0.10.0 needed some minor mods to get attached to the bluepill+ Basically, the flash code needed to be told about the STM32L443's flash size based on the unknown part number. Also the target script needed some changes. I will add more to this entry later about this.

Monday, January 1, 2018

Sunday, November 5, 2017

Ada on a $2 eBay 'Bluepill' board (STM32F103C8T6)

For about $2 a 'bluepill' board can be obtained from eBay. Taking its name from the PCB patina this small board is used in the Arduino community. The SoC itself per ST's early docs on it was 64k flash and 20k data, that said, all STM32F103C8's you buy nowadays are 128k flash. The CPU is a Cortex-M3 which can be run at 72Mhz maximum. There is a good website that shows the schematic and pinout of the bluepill:

If you are an Arduino programmer, the link above will take you where you want to go. But suppose you wanted to try programming and using the bluepill a different way? Well that is what this blog entry is about. An Ada port was done via AdaCores Ada_Drivers_Library and the Libre GNAT toolchain to the bluepill. This is preliminary work but it is able to generate working code that the author already is using as of yesterday (a garage parking measurement sensor). 

The port of the library is derivative, starting from the STM32F4 series, the STM32F1 is a 10yr old design and as such, the F4 is an extension of the the F1. The F1 has some idiosyncrasies that seem to have been addressed in the F4 (eg: GPIO setup). The port fixes the SoC freq at 72Mhz. No low power work has been done yet but I assume in a WFI setting with peripherals not enabled, it should draw not too much current even with a 72Mhz functional speed. The embedded runtimes library has 2 flavours of runtime, a full and a small footprint (sfp). With only 20k of ram, even sfp is too dear for a bluepill so a new runtime that is very close to sfp was created, called ssfp (super small footprint). This runtime strips down the stack sizes to minimums, hopefully this won't bother users too much.

There are two libraries and a development folder that are needed for development and they are on github:

Development folder:

That folder has a structure of:


Each family is a subfolder.

The development folder has F103 projects such as:

adcdma_f103 ADC using DMA
adcgpio_f103 polled ADC on GPIO
bno_f103 test of I2C to a component
crc_f103 port of AdaCore's crc test to the bluepill
lsm9ds1_f103 my preliminary 9dof sensor start. I don't plan on using it with a bluepill.
spi_f103 Interfacing SPI to an Atmel 25640 (a 64k byte EEprom).
timer_f103 Timer interrupt
try_f103 my first test prob a good start for project0
vl53l0x_f103 My garage distance project.

For debug I use openocd-0.10 and a donor Stlink v2-1 from any discovery or nucleo board.
Just the other day I got a $2 Stlink v2 usb stick from China. For that one its a 4 wire attachment:

bluepillF103.cfg: (v2 shown, invert the comments for v2-1)

#source [find interface/stlink-v2-1.cfg]
source [find interface/stlink-v2.cfg]
transport select hla_swd
source [find target/stm32f1x.cfg]
reset_config srst_nogate

Bluepill running the try_f103 program connected to the STlinkv2. This is using the Raspberry Pi3 as a Wifi openocd server as is documented on this blog.

Saturday, September 23, 2017

Stanford Mips-X @IIT

This doc may be helpful to have on hand to understand this post:

Integrated Information Technology or as that mouthful was abbreviated to: IIT (now 8x8) was founded by Chi-Shin Wang and Y.W. Sing two Weitek veterans.  Its original product line was floating point coprocessors and lived in a niche before the giant sucking sound was heard of Intel vacuuming up peripherals such as floating point. At that point, a new product was needed. From 1989 a small group was working on a Vision Processor (VP). This SIMD(4) engine took a year or so to build. Its first customer was Compression Labs (CLI). The VP had a large reg file 64 16bit regs  times 4 SIMD instances. This was so CLI could execute the 16x16 DCT. An 8x8 DCT is far more modest in reg consumption. Another customer for the part was AT&T with the videophone 2500 in 1992. So at this point, IIT had a reasonable video business with customers that had application processors that would use the VP as a video engine. About this time a search was underway for IIT's very own controller. Chi-Shin found a 'bargain' from Stanford, Mips-X for $15K! What do you get for $15K? You get a tape from Stanford's tech licensing group. Paul Chow (now EECS prof at Toronto) sat with us for a week to go over the tape. I worked with him to understand the SW tools offering. For one item, there was no C compiler for it, there was however an assembler written in Modula-2. Over the years I wrote and maintained our port of GCC to Mips-X. Having 2 delay slots broke the reorg pass of GCC on numerous occasions with the usual email chain from me to Richard Kenner and workarounds to deal with reorg issues. Finally I wrote our own 8x8 pass in GCC that fixed up cases where a hazard was in slot1 and slot2 used it. Rather than use the Modula-2 assembler, I wrote one in C. The linker, nld, in C++ (props to Jeff Loomans at a poker game who told me to learn C++, the linker was my first C++ program). From a HW standpoint, there was no schematic(!). There was a layout, not sent out(!). So, IIT sent the layout to the fab.  It came back and had severe voltage range issues. Also, it was a black box, i.e. provide a clock, take it out of reset, and it fetched a few instructions and was dead, lifeless. Observing the code, it was clear that it was a cache issue, since the first few fetches worked until a loop branched back. Luckily for us, there was a pin to disable the cache, then, it worked.
Management at IIT then reversed the layout and sucked out the design to a schematic. From there, subsequent tapeouts could use a proper core. Work then went on to produce the Vision Controller(VC) which was IIT's version of what CLI and AT&T bolted on around the VP. So this VC had a DMA controller, a video circuit, etc. Externally, it used a brooktree part for the analogue to drive the monitor. VC came back from the fab and we could see colour bars on the screen using a polled interrupt test. When that polling became a real interrupt... all hell broke loose and Mips-X crashed. The standard crash was a vertically rolling screen. Without the processor there to manage the DMA for the active portion of the screen, the DMA just kept shipping out a rolling rectangle. Doug Neubauer who was instrumental in the HW design of VC was about to go on a fishing trip and (paraphrasing) said "I am going fishing, if this is not fixed when I return, its game over". So during his trip a logic analyzer was hauled in and clipped onto the Mips-X code SRAMs. When the crash happened, the processor was in the interrupt handler, never able to leave. Mips-X has an odd interrupt pipeline. Everything is exposed to the programmer, so when you get an interrupt, the 5 stage pipe (IF RF ALU MEM WB) allows MEM and WB to finish (look out for volatiles there btw). You get 3 PC's to restart at IF, RF, ALU and the last 2 will redo the MEM and WBs that were in flight at the interrupt. If an interrupt should come in whilst an interrupt was being serviced, the final code of an interrupt handler will be:

<---------------- New interrrupt.

the new interrupt interferes with the pipleline restart and you are back in the handler with a half baked restart of the prior interrupt return. In the PSW is an e bit. The bug we found was that the exception began chain shifting again making the e bit and recovery using the chain regs impossible.

How to then return from interrupts?

Here is where I nominate a reg to the best register I have ever seen. Having worked in this space between HW&SW to nominate one reg to the best means this reg is pretty special. I believe Matt Cressa was the HW engineer who crafted it. Its reason for being even is odd. There was no need for this reg really, nor wrt its semantics. From the ERS:

RIFACE_IRQSUPPRESS    (0x20004014)                       (4 bits RD/WR)
|    |    |    |    |    |    |   |   |    |    |    |    |    |    |    |    |
|  x | x  | X  | X  | X  |  X | X | X | X  | X  | X  | x  | S3 | S2 | S1 | S0 |
|    |    |    |    |    |    |   |   |    |    |    |    |    |    |    |    |
  b15                                                                      b0

RIFACE_IRQSUPPRESS - Suppress Interrupts for S3-S0 Instructions
    0x00 = 15 instructions
    0x0e =  1 instruction
    0x0f =  0 instruction

Lets look at that reg closely, observe the inversion of S3..S0, writing 0 gets the maximum suppression. How is that useful?  Lets look at this tail end of an interrupt handler. Observe R0 being assigned to the reg.

7609c: 96c04028 st 0x4014[r27], r0
760a0: 877e0000 ld 0x0[r29], r31
760a4: 60000019 nop
760a8: f8000003 jpcrs
760ac: e8000003 jpc
760b0: e8000003 jpc

The effect is that for the next 15 instructions, interrupts are suppressed. This is enough to escape the interrupt handler without being sent back into the handler in a cooked state,

Without this we cannot get back cleanly from an interrupt. Since the reg's original purpose was unknown as the problem had not manifested itself yet it really was pure serendipity that it exists at all. Also, the inversion... Had it been not inverted and a linear map of write N get N suppress, then that too would have failed. You see at the end of the interrupt handler, the regs have to go back to the values they had at the time of the interrupt, only R0 on the Mips-X, which is hardwired to 0, can be dependably used. Amazing.

Monday, July 17, 2017

Using a Raspberry Pi3 as an OpenOCD server

Cheap and plentiful, a Raspberry Pi3 makes an excellent OpenOCD server. You can place it near your target and just attach to it from where you do your builds. On my Pi3 server I have openocd-0.9.0 and openocd-0.10.0, the win with a Pi3 is it has WLAN, so permit it to sign on to your network, preferably with a known static IP, and then you can ssh over to it and start openocd:

root@pi3:~/openocd-0.10.0/tcl# ../src/openocd --file board/st_nucleo_l476rg.cfg
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
Info : The selected transport took over low-level target control. The results mi
ght differ compared to plain JTAG/SWD
adapter speed: 480 kHz
adapter_nsrst_delay: 100
none separate
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : clock speed 480 kHz
Info : STLINK v2 JTAG v25 API v2 SWIM v14 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.262028
Info : stm32l4x.cpu: hardware has 6 breakpoints, 4 watchpoints

Now you have a connection to your board.

On another machine where you do your dev, (in my case a box in an upstairs server room) you just allow GDB to connect to it via the static IP:

admin@ubuntu_1604:/.share/CACHEDEV1_DATA/Ada/STM32/L432/dac_sw$ arm-eabi-gdb obj
GNU gdb (GDB) 7.10 for GNAT GPL 2016 [rev=gdb-7.10-ref-161-g9bf8eca]
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <>
This is free software: you are free to change and redistribute it.
See your support agreement for details of warranty and support.
If you do not have a current support agreement, then there is absolutely
no warranty for this version of GDB.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-eabi".
Type "show configuration" for configuration details.For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from obj/Debug/dac_sw...done.
(gdb) target extended-remote
Remote debugging using
0x00000000 in ?? ()
(gdb) monitor reset halt
Unable to match requested speed 500 kHz, using 480 kHz
Unable to match requested speed 500 kHz, using 480 kHz
adapter speed: 480 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08004420 msp: 0x200006d0
(gdb) load
Loading section .text, size 0xc2e4 lma 0x8000000
Loading section .ARM.extab, size 0x7c4 lma 0x800c2e4
Loading section .ARM.exidx, size 0xaa0 lma 0x800caa8
Loading section .rodata, size 0x1d50 lma 0x800d548
Loading section .data, size 0x2c4 lma 0x800f298
Start address 0x8007348, load size 62812
Transfer rate: 22 KB/sec, 7851 bytes/write.
(gdb) monitor reset init
Unable to match requested speed 500 kHz, using 480 kHz
Unable to match requested speed 500 kHz, using 480 kHz
adapter speed: 480 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08007348 msp: 0x2000cb20
adapter speed: 4000 kHz
(gdb) b main
Breakpoint 1 at 0x80007b6: file b__dac_sw.adb, line 176.
(gdb) b __gnat_last_chance_handler
Breakpoint 2 at 0x800a0be
(gdb) c
Note: automatically using hardware breakpoints for read-only addresses.
Breakpoint 1, main () at b__dac_sw.adb:176
176           Ensure_Reference : aliased System.Address := Ada_Main_Program_Name

The real win is you have 4 USB ports for connections to targets. Also the Adafruit SiLabs USB<->RS232 dongle works well so you can get UART data from an embedded target. With a Pi2 you lose one USB port for the WLAN dongle.

Here seen connected to the STM32L432 nucleo and is the target from the session show above. Also in the light blue housing we see the Adafruit usb to ttl serial cable.

Friday, July 14, 2017

Building a motion sensing camera with a Raspberry Pi

A Raspberry Pi and a pinoir camera make for a good motion capture camera. There is always an impetus to hack, the impetus in this case came from theft at my company. At night, someone had been walking off with hardware from cubicles. One was just down my aisle and was perhaps too close to home.  Not wanting any of my stuff wandering off, I wanted a camera that would record motion and save video only around the event. I did not want to process 8 hours of raw, mainly motionless video each day when I came in. Currently as it works, each file per night is about 80MB. These files are saved over the LAN on a file server, in this way not polluting the SD card on the Pi. The files can be viewed by VLC and are H.264.

For the motion sensor, an Adafruit PiR sensor is used:

This sensor needs some connectors added, a small crimp connector is added to the bare wires and hot air used to shrink a sleeve around the bare connector. For quick and dirty hacking, my goto language these days is Ruby, this project was no exception. A gem called PiPiper (Silicon Valley fans?) is used to convert GPIO levels to events that Ruby can act on. An event called out by the GPIO going high is generated by the sensor when there is motion, this in turn unblocks a forked raspivid process (raspivid being the canned camera app the RaspberryPi foundation ships). So, via 2 signals SIGSTOP and SIGCONT we can command raspivid to stop and go. raspivid has some buffering already in place so when you say SIGCONT it has the last N seconds of vid ready to go, then the camera is allowed to proceed for some grace period after the event. Finally, via a timer, the SIGSTOP is sent. In this way, smaller video files are generated.

I am using GPIO 18 for the event pin.

The setup looks as above, a PiR connected to a Pi3. On the wall is a 12v IR ring for night illumination, it glows a dull crimson as its photodetector senses darkening ambient conditions.

The code is here:

Some issues:

1) No RTC on the Pi. I have a cheap $8 Banggood kit clock thats in the field of view. That is my source of time. The console output on the Pi helps also, as long as you have the correct time set on the Pi.
2) I recommend a Pi3 as it can do more than just this and is useful to have around. I am cabled up as I found that every 30secs or so the WLAN emits a wpa supplicant pkt that couples with the sensor and generates false events.
% ifdown wlan0

3) Audio would be a plus.

ARM Cortex SVD files

A lot of goodness can be represented by an SVD file.

The full name is CMSIS-SVD and was designed by Keil who was acquired by ARM. All reasonable ARM Cortex vendors these days who want users to program their targets have SVD files. The scope of the SVD file covers all the peripherals in the designs down to the numerical addresses.

Here is an extract from the STM32L432's SVD file. We see the DAC is at a baseAddress of 0x40007400 and the first reg in the DAC is the CR or control reg at offset 0, further, we see the individual fields of the CR with EN1 at bit position 0.

      <description>Digital-to-analog converter</description>
          <description>control register</description>
              <description>DAC channel1 enable</description>

This is great info! Can we use it to make our life as programmers smoother? As an embedded programmer I use ST cores and GDB to debug them. I have put together a couple of Ruby 2.x scripts that suck in the SVD file and emit a GDB script. The first Ruby script generates this:

set pagination off
set logging overwrite on
set logging file DAC1.log
set logging on
set logging redirect on
x/256x 0x40007400
set logging redirect off
set logging off
set logging file DMA1.log
set logging on
set logging redirect on
x/256x 0x40020000
set logging redirect off
set logging off


Now in GDB you just say:

source filename 

Where filename is the output of the first Ruby script. So that's great, now we have a binary file of data for each peripheral in the SVD file as realized from our target processor when we stopped it.

It would be great if we could interpret the binary data in those dumps in a form that's human readable and can be diff'ed from one capture to another. This technique works well for binary blobs also if you can break in. You just run the next script and look at what changed in the controller. So lets look at a use case with the ADC (same part STM32L432KC):

0x50040000:     0x0000007f      0x00000000      0x10000005      0xfffe2000
0x50040010:     0x00000000      0x00028000      0x00000000      0x00000000
0x50040020:     0x0fff0000      0x00ff0000      0x00ff0000      0x00000000
0x50040030:     0x00000140      0x00000000      0x00000000      0x00000000
0x50040040:     0x00000231      0x00000000      0x00000000      0x00000000
0x50040050:     0x00000000      0x00000000      0x00000000      0x00000000
0x50040060:     0x00000000      0x00000000      0x00000000      0x00000000
0x50040070:     0x00000000      0x00000000      0x00000000      0x00000000
0x50040080:     0x00000264      0x00000000      0x00000000      0x00000000
0x50040090:     0x00000000      0x00000000      0x00000000      0x00000000
0x500400a0:     0x00000000      0x00000000      0x00000000      0x00000000
0x500400b0:     0x00000000      0x0000003a      0x00000000

After the second script:

ADC.CFGR.AWDCH1CH[4:0]: 0x1f
ADC.CFGR.RES[1:0]: 0
ADC.CFGR2.OVSS[3:0]: 0
ADC.CFGR2.OVSR[2:0]: 0
ADC.SMPR1.SMP9[2:0]: 0
ADC.SMPR1.SMP8[2:0]: 0
ADC.SMPR1.SMP7[2:0]: 0
ADC.SMPR1.SMP6[2:0]: 0
ADC.SMPR1.SMP5[2:0]: 5
ADC.SMPR1.SMP4[2:0]: 0
ADC.SMPR1.SMP3[2:0]: 0
ADC.SMPR1.SMP2[2:0]: 0
ADC.SMPR1.SMP1[2:0]: 0
ADC.SMPR1.SMP0[2:0]: 0
ADC.SMPR2.SMP18[2:0]: 0
ADC.SMPR2.SMP17[2:0]: 0
ADC.SMPR2.SMP16[2:0]: 0
ADC.SMPR2.SMP15[2:0]: 0
ADC.SMPR2.SMP14[2:0]: 0
ADC.SMPR2.SMP13[2:0]: 0
ADC.SMPR2.SMP12[2:0]: 0
ADC.SMPR2.SMP11[2:0]: 0
ADC.SMPR2.SMP10[2:0]: 0
ADC.TR1.HT1[11:0]: 0xfff
ADC.TR1.LT1[11:0]: 0
ADC.TR2.HT2[7:0]: 0xff
ADC.TR2.LT2[7:0]: 0
ADC.TR3.HT3[7:0]: 0xff
ADC.TR3.LT3[7:0]: 0
ADC.SQR1.SQ4[4:0]: 0
ADC.SQR1.SQ3[4:0]: 0
ADC.SQR1.SQ2[4:0]: 0
ADC.SQR1.SQ1[4:0]: 5
ADC.SQR1.L3[3:0]: 0
ADC.SQR2.SQ9[4:0]: 0
ADC.SQR2.SQ8[4:0]: 0
ADC.SQR2.SQ7[4:0]: 0
ADC.SQR2.SQ6[4:0]: 0
ADC.SQR2.SQ5[4:0]: 0
ADC.SQR3.SQ14[4:0]: 0
ADC.SQR3.SQ13[4:0]: 0
ADC.SQR3.SQ12[4:0]: 0
ADC.SQR3.SQ11[4:0]: 0
ADC.SQR3.SQ10[4:0]: 0
ADC.SQR4.SQ16[4:0]: 0
ADC.SQR4.SQ15[4:0]: 0
ADC.DR.regularDATA[15:0]: 0x231
ADC.JSQR.JSQ4[4:0]: 0
ADC.JSQR.JSQ3[4:0]: 0
ADC.JSQR.JSQ2[4:0]: 0
ADC.JSQR.JSQ1[4:0]: 0
ADC.JSQR.JL[1:0]: 0
ADC.OFR1.OFFSET1[11:0]: 0
ADC.OFR2.OFFSET2[11:0]: 0
ADC.OFR3.OFFSET3[11:0]: 0
ADC.OFR4.OFFSET4[11:0]: 0
ADC.JDR1.JDATA1[15:0]: 0x264
ADC.JDR2.JDATA2[15:0]: 0
ADC.JDR3.JDATA3[15:0]: 0
ADC.JDR4.JDATA4[15:0]: 0
ADC.AWD2CR.AWD2CH[17:0]: 0
ADC.AWD3CR.AWD3CH[17:0]: 0
ADC.DIFSEL.DIFSEL_1_15[14:0]: 0
ADC.DIFSEL.DIFSEL_16_18[2:0]: 0

So now we can study the regs with symbolic output when we consult the datasheets for these types of controllers.

The Ruby scripts are here: