Hardware Data Logger based on STM32 Nucleo and ESP Module

It's been a while since I last posted here, but here I am with something new. This project started when I was cleaning up the repo for a semiconductor radioactivity detector. For that project, I made a small shield for the STM32 Nucleo that included a button and a display.

It was used to present measurements in real time on the display and send them to a Raspberry Pi for further processing. I thought this could be turned into a separate project and reused, and I'll be presenting the results of that in this post. Here is the link to the GitHub of this new project that I describe in this post.

I started working on a new version and came up with these specs:

  • Keep the STM32 Nucleo as the main microcontroller
  • Add an ESP module for remotely uploading data to a Raspberry Pi or any other device
  • Use a much larger color display
  • Include four buttons for interacting with the device
  • Add 4 BNC connectors for gathering data from other devices
  • Include an SD card slot for local data storage
  • It also has a light sensor to dim the display at night.

The picture below shows the version that includes these specifications. Fun fact is that I traveled with this device in my carry-on luggage and was worried there might be issues at security, since it kind of looks like a movie bomb, but everything went fine.

Based on the that version, I created a dedicated PCB. I thought assembling the PCB would be pretty simple, but unfortunately, I forgot to buy all the components, which made it much harder than I expected. In this version, I used SMD female pins to connect to the Nucleo board. However, since I didn't have them on hand, I used THT pins instead. The footprint was different, and I didn’t solder them well, which put a lot of stress on the Nucleo board when connecting the shield.

It kept breaking repeatedly, and I didn’t expect such a small issue to cause so many problems. The LCD no longer works—probably something broke again—but the rest of the setup is functioning. In this version, the STM and ESP communicate with each other, but only in a limited way.

The device was designed in KiCad, and the circuit is shown below.

The firmware was developed using STM32CubeMX (for peripheral configuration), CMake, and C++17. I didn’t use anything from the standard library or dynamic memory allocation.

I’ve also included several tools for checking code quality: unit tests (Google Test, Google Mock), code coverage, static code analysis (cppcheck), and even dynamic code quality checks (which don’t make much sense in my case since I explicitly avoid using dynamic memory allocation, but I wasn’t thinking about that at the time). There are also Doxygen comments and coverage reports to flag any missing documentation.

I used ChatGPT (paid version) for most of this configuration. It’s amazing but can also help you quickly generate garbage. It speeds things up when you're headed in the right direction, but if you're doing something silly, it will just help you do it faster. You can check out how the CMake files are written in this project to get an idea of what I mean.

The current version is not finished, and I think I’ll abandon it in favor of a new version. I plan to drop the Nucleo board and place the STM chip directly on the PCB. This will solve the connector issues and make the device smaller and cheaper.

Another idea I have is to use some slots (similar to the ones used for connecting RAM to a PC motherboard, though I haven’t researched the names or footprints yet). This way, I could have one main PCB with the STM, ESP, display, etc., and use detachable boards for acquisition modules.

This is just the beginning, and there’s plenty of room for improvement. If you're curious to see where it goes, don't forget to watch or star the project on GitHub!

Ionization Chamber

An ionization chamber is a device used to measure radioactivity levels. When air atoms are hit by radioactive particles, an ion pair is produced. Ions have an electric charge, and if they are in an electric field created by positive and negative electrodes, negative ions will move toward the positive electrode, and positive ions will move toward the negative electrode. They will attempt to "meet each other," thereby creating a current. This current can be measured and is proportional to the number of ion pairs. The number of ion pairs is proportional to the radioactivity level.

The architecture of the device is presented below. It consists of an analog part and an STM8 microcontroller, which collects and sends measurements via UART. These measurements are collected on the Raspberry Pi side and processed using Python and R scripts. The results are stored as .csv files (raw data) or .png files (diagrams). While it would be possible to simplify this setup and eliminate the Raspberry Pi, I wanted to enable data collection and flashing of the microcontroller remotely, without needing to be physically near the device.

The outer electrode of the ionization chamber was made from PCB scraps and a copper plate, while the inner electrode was constructed using a few centimeters of non-enameled wire. To avoid electromagnetic interference, the amplifier was placed inside a metal chassis.

High voltage is required to create a sufficient electric field in the chamber. Initially, I was unsure of the exact voltage needed, so I added a simple DC/DC converter to the PCB to generate 400V DC. However, tests showed that 4x12V from batteries is sufficient. As a result, while the DC/DC converter is soldered onto the board (visible on the bottom left side of the picture), it is not in use.

The software was written in C and compiled using SDCC. A strange limitation of SDCC is that even if functions are unused, they are still compiled and included in the final binary. Since I am using StdPeriph as a HAL, there were many unused functions that occupied space. To work around this, I added #ifdef 0 ... #endif around each unused function, then attempted to compile and uncommented the functions that were actually needed.

The diagram below shows the results obtained from the device. As can be seen, the device is quite sensitive, capturing even small variations in the measurements.

For more details on the project, feel free to check out the full source code and documentation on GitHub. You can also follow the project's development and updates on Hackaday.

Camera nuclear-radiation sensor: part I

In previous posts I've described a radioactivity detector based on a photodiodes. Image sensors in cameras use photoelements too, so I think that they could be also used to detect radioactivity. At this moment i didn't success in this , nevertheless I will describe here my attempts.

I'm using RaspbberyPI, to get data from the sensor, camera is some low-budget/quality clone designed for RaspberryPI. I have removed optics, and covered whole camera in black tape to block any light coming to the sensor. It's visible on the picture below.

To handle the camera on the software side, I'm using Python script (with PiCamera library). In infinitive loop it takes a photo and calculate sum of pixels value and then sums values of each RGB channel. This value with timestamp is put to CSV file that is later parsed to diagrams using R script.

Without any sample, internal noise of the sensor (and maybe background radiation) should give after some time (e.g. after a couple of hours) a Gaussian curve on the histogram. After putting measured sample to the sensor and waiting similar period, a new Gaussian curve would appear, so that the histogram would have to visible peaks. That was my assumption, hit would prove that the sensor works, however as visible below it doesn't - there is only one peek.

I will try to better isolate the sensor also from electromagnetic noise or maybe buy a new camera (less noisy). Other than that I don't have ideas to make it works.

Weird arrays in C

Recently I've saw quite strange way to create an array in C. I will describe it bellow - looks quite interesting!

Let's assume we have an array given bellow:

int myArray[4];
We could rework it, so that each element is declared separately:
    int myArray0;
    int myArray1;
    int myArray2;
    int myArray3;

Now we can obtain pointer to first element and last element, and iterate through elements in between it as with regular array. We exploit that compiler will probably put those variables in the same order in the same place in memory.

A full example is given below:

#include <stdio.h>

void processElement(int e)
{
    printf("processing element in array, it has value %d\n", e);
}

int main()
{
    int myArray0 = 0;
    int myArray1 = 1;
    int myArray2 = 2;
    int myArray3 = 3;

    // dummy way to force compiler not to optimalize-out our array members
    printf("%d\n%d\n%d\n%d\n", &myArray0, &myArray1, &myArray2, &myArray3);

    int *start = &myArray0;
    int *stop = &myArray3;

    if(start > stop)
    {
        start = &myArray3;
        stop = &myArray0;
    }

    for(int* it= start; it <= stop; *it++)
    {
       processElement(*it);
    }
}

:)

Semiconductor nuclear-radiation sensor: part III

In this post I will present a new hardware version of my sensor, older versions are described in part I and part II. In comparison to the previous one, sensitivity is roughly x10 more sensitive.

In previous version, tin foil window for photodiodes was very close to the BNC sockets and because enclosure was small, it was hard to place a sample close enough. Not it's better, however, if I would choosing again, I would use metal enclosure similar to those used in PC oscilloscopes and put BNCs on front panel, power socket on rear panel and tin foil window on top. This would allow me to easier access for debugging- now I have to desolder sockets to get to photodiodes or to bottom side of PCB (fortunately this side is empty).

Bellow you can see the diagram (click on it to enlarge), what has changed in comparison to previous version:

  • One additional photodiode (previous version has only two of them) to increase sensitivity, also the window in enclosure is much bigger
  • x10 bigger resistance of the feedback loop resistor in first stage amplifier, I tried bigger, but then osculations started
  • Bias for photodiodes using 12V batteries, I could increase it, but didn't have enough space in this enclosure
  • Buffer op-amp at the analog output
  • Digital output.

Additional changes not shown on diagram:

  • U1 is OPA657U
  • U2 is OPA656U
  • R4 is 1G
  • As close as possible to input power socket are placed in paraller 1n/16V and 100n/16V, without them the device started to oscillate randomly.
  • A Schottky diode is connected in series after mentioned above extra capacitors to reduce risk of damaging the device when power supply is connected incorrectly. I don't know if it will help enough but I have already broke one PCB of this device this way, so now it's there.

Digital output is 12V in high state, 0V in low state, this is not very useful for 3V3 logic microcontroller that I'm using for data acquisition, so I made a simple converter using additional PCB.

Here it is visible soldered. I like in those SMA Female sockets that they can be soldered to the edge of the PCB (as visible below) and this is still quite mechanically stable, but doesn't require to drill holes as in regular mounting way.

All materials (including software part presented in previous post) for this hardware revision can be downloaded from project's repository.

Semiconductor nuclear-radiation sensor: part II

There are many ways to measure radioactivity level, semiconductor detectors sense interactions between ionizing radiation and p-n junction. Because in hobbyist area most popular are Geiger-Muller based detectors (in short: not a semiconductor but lamp based devices), I think it's a cool idea to take a look at this approach.

In this post I will present such home-made sensor and a set of software to parse collected results.

Picture below presents circuit of the sensor that I made, it consist of a photodiode that acts as a sensor, transimpedance amplifier and "regular" amplifier. I've selected op-amps that has little input noise.

Changes that I made during testing the device, that are not presented on the diagram:

  • D1 is BPW34
  • U1 is OPA657U
  • U2 is OPA656U
  • 2p capacitor is connected in parallel with R6
  • 820k resistor is connected between U2 out and BNC socket.

Below is visible sensor in enclosure and without it. Because the device is sensitive for background EMI and light noise, a metal housing is needed. To increase exposure of the sensor to the radiation, where it is, in the housing, a hole is made and just a tiny adhesive metal tape is covering it.

Collecting data from measurements devices using USBTMC, SCPI, Python and R

High end test gear allows two-way communication with PC to set measurement parameters and to send the measured data to PC for further analyze. On PC side there are applications for this (LabView, BenchVue), but they are expensive. One option is to make own program/script that will communicate with the device and parse/present measured data - this approach I will present it in this post. The tool I'm using it with is 34460A(digital multimeter).

Below is an example of the measurement graph made using this home-made software.

The module of communication with the device I made in Python, it stores data to .csv file, that is later parsed by a script in R and the output graph is stored as .png image. The R part could ass well do some numeric stuff and print it on STDOUT, but for me that was not the case.

Initially all was in Python, but I didn't like Python's graphs, they weren't visually nice.

Python part when started will save measurements to the .csv file constantly, at any time, user can run R script to create new graph.

Installation steps

Assuming device driver correctly enumerate in the Device Manager (if not one needs to install its drivers first).

  • Install Python and R, add them to Path (environment variable).
  • In R, install following packages: latticeExtra, gridExtra, grid.
  • In Python install following packages: usbtmc, pyusb.
  • On Windows, download libusb-win32 and follow README steps to bind it to the device. In addition it didn't work for me if I didn't run the tool as an Administrator. If everything went ok, device will be visible in Device Manager in branch named libusb-win32 devices.
  • Download Python/R scripts, they are available via my IonizationChamber repository.
  • In main.py, replace idDMM with id of the device.

For usbtmc, I got the same problem as described here, fortunately the fix presented in that thread fixed it, so please check it if you have some weird stacktrace errors in Python.

Usage

I made those scripts for my personal use, so they may be a bit awkward to work with, but once the setup is done, everything should work fine.

  • Set meassurement type and range on your device (this could be also done via python script)
  • Start python script
  • Run R script, it will create results.png file with graphs.

That's all.