Whats Up V2

by mars91 in Circuits > Electronics

440 Views, 5 Favorites, 0 Comments

Whats Up V2

IMG_0558.png
IMG_0555.png
Screenshot 2026-08-21 at 5.21.53 PM.png
Screenshot 2026-08-21 at 5.21.33 PM.png

This is version 2 of my What’s Up circuit. A portable circuit that uses only GPS to determine what planets are overhead. V2 has a more powerful STM32, planet calculations for five years, polynomial fitting, very good accuracy, LiPo battery, (something called) parallax correction, and more in this keychain sized circuit.

I enjoyed version 1 of my What’s Up circuit, but when I would see it solve the overhead planets, I was never 100% sure they were correct... Writing the code for the V1 ATtiny3216 with no debugger, and a lot of trying stuff until something worked, always made me feel uncomfortable.

These factors led me to share a version 2.

Supplies

Screenshot 2026-08-21 at 5.21.09 PM.png

BOM


  1. 4 2.2k resistors - 0603
  2. 4 10k resistors - 0603
  3. 9 SK6812-EC20 RGB LEDs
  4. 4 0.1uF capacitors - 0603
  5. 2 LEDs - any color 0603
  6. 1 SAM-M10Q GPS module
  7. 1 100k resistor - 0603
  8. 1 USB-C receptacle - SAMESKY UJ20-C-H-G-SMT-1-P16-TR
  9. 1 TPS7A0230PDBVR - SOT-23-5
  10. 2 5.1k resistors - 0603
  11. 3 4.7uF capacitors - 0603
  12. 2 buttons - gull-wing 5.2mm
  13. 1 NSR0320 diode - SOD-323
  14. 1 DMP2045U MOSFET - SOT-23
  15. 1 SiP32432DR3 - SOT-363 / SC-70-6
  16. 1 STM32G031K8Ux MCU - QFN-32 5×5mm
  17. 2 1uF capacitors - 0603
  18. 1 MCP73831-2-OT - SOT-23-5
  19. 1 2N7002 MOSFET - SOT-23
  20. 1 JST S2B-PH-SM4-TB 2-pin connector
  21. 1 lipo
  22. 1 custom PCB - more on this below

Order Board Here


link to PCB

Programming software

  1. STM32CubeMX
  2. STM32CubeIDE

Programmer Tools

more on programming below

  1. STLINK-V3MINIE
  2. Tag-Connect TC2050-IDC-NL-050-ALL | Normal orientation.
  3. TC2050-CLIP-3PACK retaining clip set
  4. custom 14-pin-to-10-pin adapter PCB
  5. 100 Ω 0603 resistor.
  6. Samtec FTSH-105-01-F-DV-K-TR
  7. Samtec FTSH-107-01-L-DV-K-P-TR
  8. custom 14 to 10 pin stlinkV3Minie PCB

The complete programming connection is:

Computer -> STLINK-V3MINIE -> custom 14-pin-to-10-pin adapter PCB -> Tag-Connect TC2050 cable -> alorAtMe PCB

Soldering

  1. 110V 850W Soldering Hot Plate (this one is big - i'm sure a small one would work)
  2. Solder Paste
  3. Solder Flux
  4. Stencil Squeegee

SAM-M10Q GPS

pexels-zelch-30596893.jpg
Screenshot 2026-08-21 at 5.27.17 PM.png
IMG_0560.png

This project uses STM32CubeIDE with UART serial printing and CubeIDE debugging. That freedom led me to spend a few days building and optimizing this GPS I2C code.

Highlights

The provided file "sam_m10q.c" also has a lot of comments while I was learning/writing it.

  1. From the datasheet, the GPS uses a fairly fast I2C communication at 300 kHz. This was set up in STM32CubeIDE and is also why the I2C pull-up resistors are 2.2k instead of the more typical 10k.
  2. Once the module is powered and locked onto satellites, it starts filling with data. The overhead GPS satellites constantly beam down data that gets picked up by the onboard antenna. It's (we) the programmer's job to flush that data correctly and determine when an NMEA sentence starts.
  3. A NMEA sentence is plain-text output from a GPS receiver containing location, time, satellite data, etc., formatted to the NMEA standard. The process of flushing and building an NMEA sentence can be found in my "SAM_M10Q_Read_GGA" function.
  4. Once you have an NMEA sentence, you need to parse something that looks like this: GPGGA,181908.00,3404.7041778,N,07044.3966270,W,4,13.... I wrote my own parser, but knew I was reinventing the wheel, so I'm now using a very popular lightweight C repo from GitHub: https://github.com/kosma/minmea And it worked.

Here is the C struct that gets data when the GPS is working:

typedef struct
{
uint8_t valid_fix;
uint8_t fix_quality;
uint8_t satellites;
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint16_t millisecond;
double latitude;
double longitude;
double altitude_m;
} SAM_M10Q_Data;

What else do you need :)

Backup Battery

The backup battery is also important. This GPS locks onto satellites (ultimately solving what planets are overhead) and it locks much faster when a backup power is supplied.

The datasheet (Absolute maximum ratings page 11) explicitly says to keep the backup voltage below 3.6v, so a fully charged LiPo cannot be connected directly or it would damage the GPS. So the LiPo backup power also goes through a high-efficiency TI 3V LDO.

Power and Programming

IMG_0570.png
IMG_0571.png
Screenshot 2026-08-21 at 5.21.09 PM.png
IMG_0562.png

The Latch

Like many of my circuits, it uses the SiP32432DR3 to form a latching circuit. The SiP32432DR3 does a great job of using only nanoamps when the circuit is OFF and easily letting power through when told.

When the user presses the ON button, the STM32 gets power, turns on, and uses one of its GPIO pins to latch onto the SiP32432DR3. This latching keeps the GPS, LEDs, and itself (the STM32) powered.

Press the other onboard button and the STM32 GPIO releases the latch turning the circuit off.

The LiPo and USB-C charger also need isolation, which is done with a P-channel MOSFET. There are more detailed notes in the schematic.

My only remaining problem is I need to keep the STM32 powered while programming it, but the SiP32432DR3 latching system itself needs the STM32 already programmed... If that makes sense. So I used an N-channel MOSFET that essentially acts like someone pressing the ON button whenever USB-C is plugged in. So when programming you must have the USB-C plugged in.

There are basically three ways power stays on:

  1. USB-C “presses” ON through the N-channel MOSFET.
  2. The user presses and holds the ON button.
  3. The STM32 keeps itself ON after it first gets a surge of power.

Programming

For programming this board, every square millimeter matters. This is a tightly packed, one-sided board, so I wanted the smallest programming footprint while keeping programming easy.

My solution is a custom intermediate PCB that takes the STLINK-V3MINI connector and maps it down to only the pins I actually need.

The STLINK-V3MINI has a 14-pin connector, but I only need seven pins to program and debug my STM32. So I use the 10-pin Tag-Connect programmer. 10 pin Tag-Connect programmer is more than 7 pins but less than the STLINK-V3MINI 14-pin connector.

If Tag-Connect made an 8-pin version, I probably would have bought that.

These exposed programming pins let you debug, upload, and UART print.

GND, V-SENSE, SWDIO, SWCLK, NRST, UART RX, UART TX

So now in my apartment I have several simple custom PCBs whose purpose is to route a 14-pin connector to a 10-pin connector.

The complete connection looks like this:

Computer->STLINK-V3MINI->custom PCB with 14-pin connector->custom PCB with 10-pin male connector->Tag-Connect TC2050 cable->my circuit board

You may notice the PCB adapter in my pictures looks different from the provided PCB links. The version in the pictures was an earlier design with several parts never installed because of an error. The PCB files and schematics I provided show the version you actually need.

The TC2050 Tag-Connect has retaining clips called the TC2050-CLIP-3PACK Retaining Clips. They hold the cable firmly against the PCB while programming it.

Between the STLINK-V3MINI, custom PCB, and Tag-Connect cable, I expect the complete programming setup to cost about $100 USD.

Hopefully it's a one-time purchase that gives you a small, high-quality programming and debugging connection for future STM boards.

RGB LEDs

IMG_0551.png
IMG_0550.png
IMG_0553.png
Screenshot 2026-08-21 at 5.29.44 PM.png

The RGB LEDs use PWM & DMA. If you're asking what is PWM DMA, no worries. First, there is this amazing video on PWM DMA RGB LEDs with STM32: https://www.youtube.com/watch?v=MqbJTj0Cw6o&t This is also just an amazing channel in general if you haven't seen it before.

Overview

My basic summary is that PWM, or Pulse Width Modulation, lets you create a periodic bit-stream of HIGH and LOW pulses where you control how long the signal stays HIGH. One PWM period or pulse represents exactly one bit, either a 0 or a 1.

Sounds straightforward, but you are completely at the will of the component/LED datasheet. These LEDs expect the HIGH and LOW portions to be a certain number of microseconds (millionths of a second).

This is where I start thinking: where is this HIGH part in the pulse of the PWM period? At the start? The end? The middle? These LEDs specifically say the HIGH signal needs to be at the start of the PWM pulse which is set up in CubeIDE and firmware.

Set-up

The datasheet says the data rate (bit stream) needs to be 800 kHz; sending a 0 needs to be HIGH for a minimum of 0.2 µs; a 1 needs about 0.58 µs; and reset signal also needs to be LOW for more than 80 µs.

The math is very straightforward once you know your STM32 clock speed. We need to

  1. Set-up PWM for the LEDs required 800 kHz
  2. Solve for the leading HIGH time for a 1 and 0

The STM32 clock is 16 MHz. One tick takes: 1 / 16000000 or 0.0625 µs per tick.

To get the required 800 kHz, I set (in code) 20 timer ticks per PWM period. So: 20 ticks * 0.0625 µs = 1.25 µs. And 1 / 1.25 µs = 800 kHz.

0 bit value needs a minimum HIGH time of 0.2 µs. Timer tick of 0.0625 µs gives 0.2 / 0.0625 = 3.2 ticks. Round up and use 4 ticks. This value of 4 is called the compare value for sending a 0. For sending a 1, use a compare value of 10, 10 ticks * 0.0625 µs = 0.625 µs.

Oscilloscope

I used my oscilloscope to troubleshoot something (forget what since I wrote the LED code weeks ago) but I did take some pictures I can share.

When first setting up my scope, it was reporting a delta voltage of around 5 volts (before I properly triggered the scope to see the signal). I thought, great I'm frying my STM32 with 5 volts. Turns out it was a ringing from the fast edge of the PWM signal. Similar to a mass-spring system overshooting, circuits contain their own “springs” and “masses” in the form of capacitance and inductance. A very fast signal edge can overshoot in voltage and take time to settle. It's physics and not damaging.

The pictures do show a basically perfect 800 kHz signal and captured the board sending a 0 data pulse at about 0.252 µs!

24 bits GBR

Each LED uses a value from 0–255 for R, G, and B. It takes 8 bits to represent 0–255, so for each color, one LED needs 3*8=24 bits in total. It takes 24 PWM pulses to tell one LED what RGB values to display. If you send 48 bits, the first LED takes the first 24 bits for itself and passes the remaining 24 bits to the next LED.

With 9 LEDs, one full write takes: 9 * 24 = 216 bits or 216 PWM pulses. At 800 kHz it takes 216 / 800,000 ≈ 0.00027 seconds to write all 9 LEDs. Not bad! The first bit sent is the most significant bit, and these LEDs expect the data in G-R-B order

DMA

This PWM data needs control at a precise timing so we use DMA. DMA (correctly) stands for Direct Memory Access. Instead of making the core STM32 CPU manually trigger all 216 PWM values at exactly the right moments, you can build the entire data sequence ahead of time in an array and let DMA feed those values on a timer, freeing the CPU.

In "sk6805.c" you'll see the DMA transfer here:

HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, pwm_data, PWM_BUF_LEN);

DMA works its way through pwm_data[] and keeps putting the next compare value into TIM2.

When DMA reaches the end of the buffer, the STM32 calls the PWM-finished callback function. I would look at both sk6805.h/.c at my code notes from more info.

An Issue

One surprising need is this:

while (!sk6805_done){}

For typical RGB testing, I never saw an issue for days. It wasn't until I built fast, fancy LED animations I noticed problems. Tho uncommon (every 10 seconds or so) a rogue LED would glitch, fail to clear, suddenly get extremely bright, or display wrong colors. My perfect PWM and DMA was failing somewhere...

I think the DMA frame was not completely finished before the LED code started a new one, so bits were getting mixed together... (I think)

Calling "while (!sk6805_done){}" ensures the DMA transfer has finished before the next. Its fixed the glitch. But (!sk6805_done){} is blocking. Tho, I'm fairly confident it only blocks for an extremely small amount of time.

STM32 Astrophysics Time

pexels-karola-g-6256253.jpg
IMG_0545.png
Screenshot 2026-08-20 at 1.06.02 PM.png
IMG_0565.png

Wait, no astrophysics?

In V1, there was a lot of onboard orbital mechanics: storing positions and velocities and then propagating them forward with Newton's F = ma. In V2, the STM32 does something very different.

It does not calculate planetary orbits using Newton's laws. Instead, it uses data science. The STM32 gets a tiny compressed mathematical approximation of the results. V2 is less orbital mechanics on a chip and more linear algebra on a chip.

Data Fitting

Imagine you have a bunch of noisy data points and you think they follow a straight line:

y = mx + b

You know all of the x values and messy y values, but you do not know m (slope) or b (offset)

Rewrite this as a matrix problem:

Mv ~ y

where M contains the x values and v contains the unknown variables m and b. Computers find m and b that minimize the error between your assumed line and data. The details go beyond what needs to be said in this write up, but IMHO this is one of the jewels of linear algebra.

Ultimately, data fitting takes data and gives you a best-fit function. You can then input any x value (in this case, time) and the best-fit function will output the result, like the position of a planet.

Straight Lines to Chebyshev Polynomials

I read plenty of astro material saying Chebyshev polynomials are better than ordinary polynomials for this kind orbit data. The folks at NASA/JPL famously use Chebyshev polynomial approximations for their ephemerides. So I use them ̄\_(ツ)_/ ̄

Chebyshev polynomials use the same idea as straight lines.

A normal polynomial looks like a0 + a1x + a2x² + a3x³ + ...

Our earlier equation of y = mx + b is really just a first-degree polynomial a0x⁰ + a1x¹ with a0 = b and a1 = m.

Instead of using ordinary powers of x, we use Chebyshev polynomials.

A Chebyshev fit looks something like: f(u) = c0T0(u) + c1T1(u) + c2T2(u) + ...

where the T terms are polynomial functions and u is scaled so the time interval runs from -1 to +1.

Chebyshev example:

  1. T0(u) = 1
  2. T1(u) = u
  3. T2(u) = 2u² - 1
  4. T3(u) = 4u³ - 3u

and so on.

Why I Actually Used Chebyshev Polynomials

Here is a small rant.

I wanted to convince myself instead of blindly accepting that “the smart people use Chebyshev fits” and maybe learn more about them myself.

So I rewrote my entire Python code using ordinary polynomials instead of Chebyshev and... ordinary polynomials work very well too. I still get extremely good accuracy. I'm sure there is some fundamental, deep mathematical reason why Chebyshev polynomials are better for this kind of fitting, but I wasn't able to convince myself.

What sold me on the Chebyshev representation was not some accuracy improvement but the coefficients.

For my normalized direction vectors, the Chebyshev coefficients stay very small. This allowed me to do a crucial memory trick.

When generating the ephemeris, I allow a coefficient range of ±1.15. If you edit the Python ephemeris generator and push beyond that 1.15 value, you'll get an error and need to adjust either your fits or the allowed range.

Then these small coefficients can be mapped into signed 16-bit integers! Instead of storing every planet coefficient as a 32-bit or 64-bit floating-point number, I store int16 values. Then the STM32 reverses the scaling when it uses the polynomial.

From my (approximate) calculations, this int16 representation saves over 31 KiB!!! A Huge critical memory improvement for the poor (but powerful) 64 KiB STM32.

Building the STM32 Ephemeris

The Python script does all the work. Who wants to write that in C anyways. First, it loads the JPL DE440s ephemeris with Skyfield. Next, the Python code divides the five-year ephemeris into chunks. This can be adjusted to your liking. Different planets get different polynomial degrees and segment lengths (chunks).

The Moon changes quickly, so I use degree-5 polynomials over 12-day segment lengths. Neptune changes much, much, much more slowly, so I can use degree 3 over 160-day chunks.

The script automatically generates master_ephemeris.c. That C file contains the coefficient arrays plus a small amount of data telling the STM32 each body's polynomial degree, segment length, and number of segments.

It also generates a calendar lookup table so the STM32 can cheaply turn a UTC date (from the GPS) into seconds since the ephemeris coefficients were made. The STM32 needs the time difference (in seconds) between the creation time of Chebyshev coefficients and the GPS received UTC time. This is also a major win. Before storing the calendar lookup in the ephemeris, I found myself writing a leap-year function in C, days in a month function in C, etc. Now, let Python figure out how many days have passed since the ephemeris was generated.

Everything is documented in the code, take a look if you're curious.

What the STM32 Actually Does

When the GPS receives the current UTC time, the STM32 converts it into elapsed seconds from the beginning of the ephemeris. From that, it finds which Chebyshev segment contains the current time for every planet. The STM32 evaluates the Chebyshev series (converting the int16 coefficients back into floating-point values). After evaluating X, Y, and Z, the STM32 has a very very accurate direction of the planet.

But...We live on a fast-spinning ball.

So There is Astrophysics

First, data stored about the planets is in an inertial reference frame (The X Y Z solved from Chebyshev). In space talk, this is a ECI frame. It's useful because it is closer to how you naturally imagine planets. Their motion is slow and smooth in ECI.

But we live on a fast-fast-spinning ball.

So after finding a planet's position in the ECI frame, we need to rotate that position to match what we actually see from Earth. The Earth-fixed coordinate system is called ECEF.

So why not just store the ECEF values in the first place and solve with Chebyshev?

Those values are zooming. They include both the planet's motion through the solar system and Earth's daily rotation. That extra rotation makes the positions change much faster, which makes the data harder to fit efficiently with low-degree polynomials.

So during the c codes ephemeris generation, Python also builds the rotation matrix for needed for the ECI-to-Earth-fixed rotation.

We need only one rotation matrix because Earth spins very consistently, with a sidereal day of about 86,164.0905 seconds. Earth is slowing down a few milliseconds every 100 years. So if you see this write up in 2126, maybe check the sidereal day length. But we're fine for our lifetimes.

The Final STM32 runtime

After GPS receives UTC time

  1. find Chebyshev segment
  2. evaluate X, Y, Z to get the ECI direction
  3. rotate with Earth to get the ECEF direction

Then the GPS latitude and longitude builds your local vector for your position on Earth. Latitude and longitude are, by definition, a ECEF frame, the same ECEF frame we spent all this trouble getting into. Using some simple vector math, the planets ECEF direction, and your local “lat lon” vector gives the planets altitude! The degrees you tilt your head above the horizon to see the planet.

Positive altitude means the object is above the mathematical horizon. Negative altitude means it is below the horizon and not above you. And the corresponding LED turns on. Voilà. GPS Planet Overhead.

The Moon...

The Moon gets one extra step because the Moon is so close.

Look at my exaggerated drawing.

Hopefully it shows how the Moon suffers from something called topocentric parallax. Like everyone else, I had never heard of topocentric parallax. My moon had a much bigger error than any other planet. First, I thought the problem was the polynomial coefficients or the rotation calculations, but it was easy to show the Moon's vectors were just as accurate as the other planets. Then I found the real issue. The Moon is so close to Earth, its apparent position can shift depending on where you're standing on Earth's crust. In other words, topocentric parallax.

To overcome it, I use Earth's radius and the Moon's average distance to shift the viewpoint from the center of Earth out to the observer's position on the surface. This corrects the Moon's parallax error and brings the overhead result back up to over 99.9%. This is also documented in the c/.py code below

The other planets don't need this correction because they are so far away.

Accuracy & a Bonus

My apartment is near sea level, but I consistently get over 99.9% when testing the circuits approximations against truth values. I could only generate errors when I tested tens of thousands of points near the horizon. The finished code doesn't have my troubleshooting steps, but during development I confirmed values like the Chebyshev segment, u value, elapsed seconds, planet positions, etc. over UART. The STM32 consistently returned the same results as the Python code.

It's so accurate that part of me feels bad simply “throwing away” all the data just to display whether a planet is overhead.

As the Moon was rising from my location, the STM32 was printing the change in altitude every second. I could literally watch the calculated Moon altitude rise by 0.002 degrees per second as I watched it from my window.

Actual testing data over UART as the moon rose from my horizon.

moon alt=-0.028540
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:43.000
moon alt=-0.025908
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:43.000
moon alt=-0.025908
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:44.000
moon alt=-0.023287
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:44.000
moon alt=-0.023287
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:45.000
moon alt=-0.020639
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:45.000
moon alt=-0.020639
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:46.000
moon alt=-0.018009
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:46.000
moon alt=-0.018009
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:47.000
moon alt=-0.015369
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:47.000
moon alt=-0.015369

Maybe I can think of another circuit someday that takes advantage of this resolution.

Bonus

Hold the OFF button for three seconds and the circuit will show your latitude and longitude.

It displays latitude and longitude in quaternary, or base-four, representation:

  1. 0 = OFF
  2. 1 = Red
  3. 2 = Green
  4. 3 = Blue

For example, if you're in New York City you should see B B B G R B (longitude) for 5 seconds and then R B _ B G G _ (latitude). A white LED marks the end of the number.

You can try to figure out the code yourself, but if you ever want this circuit to tell you your latitude and longitude (to ~1km area of uncertainty!), just hold the OFF button for three seconds.

I won't show a picture of my LEDs displaying my RGB latitude and longitude, as I don't want you to know where my apartment is :)

V2 Vs V1

  1. Accuracy and speed are big ones. V1 was slower and could be blocking. V2 is much more accurate, has a larger ephemeris, and much more extensive testing.
  2. V2 also uses a LiPo instead of the 3V LS14250. TBH, not sure which battery I like better. My 400 mAh LiPo takes up less space, but it also requires extra components to support charging. LiPos also lose charge over time. The LS14250 has a great shelf life. I think I'm actually leaning back toward the LS14250.
  3. V1 used WS2812B-2020 addressable RGBs, while V2 uses SK6812-EC20 RGB LEDs. Both are 24-bit RGB-class addressable LEDs and, from what I knew, the SK6812-EC20 was newer, so I went with them. Turns out their light-scattering angles are very different. The V1 LEDs have a much larger scattering angle, so it is easier for the colors to visually mix into yellow, purple, etc. The SK6812-EC20s look more like sharp individual red, green, and blue dots sitting next to each other instead of nicely blended colors. My Venus color does not look yellow with the SK6812-EC20 LEDs... These two “2020” LEDs do look dramatically different despite having essentially the same electrical form factor. Another lesson learned. V3 will definitely be back to using the WS2812B-2020.

Assembly

Tips

Align the stencil over your board. Spread solder paste over the holes and use a squeegee to push the paste through. Make sure the stencil is flush against the board and everything is nice and clean.

Next, carefully place all the components in their correct spots.

Reflowing with a Hot Plate

Here’s my process:

  1. I place the board, with all components and solder paste, on the hot plate while it's off.
  2. Then I turn it on and wait for the temperature to reach 215°C.
  3. Around 200°C, you'll see the solder start to melt.
  4. Once it hits 215°C, turn the hot plate off.
  5. Carefully move the board (I use needle-nose pliers) onto a cooling surface (like a cookie sheet).

Post-Reflow Cleanup

Check for solder bridges, unintended blobs connecting pads. If you spot any:

  1. Dab some flux on the bridge.
  2. Use a hot soldering iron and gently drag across the area.
  3. Clean off the flux using isopropyl alcohol and a Q-tip. Burnt flux is sticky and turns yellow-brown.

Also, keep your soldering iron tip clean by wiping it with some solder and a damp sponge.

STM32 CubeIDE and Code

Screenshot 2026-08-21 at 5.50.31 PM.png
Screenshot 2026-08-21 at 5.51.16 PM.png
Screenshot 2026-08-21 at 5.51.43 PM.png
Screenshot 2026-08-22 at 10.39.15 AM.png

STM32CubeIDE overview of the of GPIOs used:

  1. UART TX/RX pins, configure USART2 for 115200 baud, 8 data bits, no parity, 1 stop bit, TX/RX mode.
  2. User button pin as a normal GPIO input with no internal pull-up/down.
  3. Power latch pin as a push-pull GPIO output, no pull resistor, low-speed output. Start it LOW.
  4. I2C1 on the SDA/SCL for 300 kHz. Keep 7-bit addressing.
  5. TIM2 Channel 1 as PWM output. This build uses TIM2 CH1 for the addressable LEDs and drives it with DMA. The code uses Prescaler = 0 and Period = 19.
  6. DMA for TIM2 CH1 and its DMA interrupt.
  7. TIM3 as a basic timer with interrupts. My attempt to user a timer and interrupt for GPIO debouncing. The current setup uses Prescaler = 15999 and Period = 9. Button is checked every 10ms. (Period+1) * (clock speed) / (Prescaler+1) gives 10ms.
  8. TIM3 is a period callback just sets a flag for the OFF button

Grab minmea.c/.h from https://github.com/kosma/minmea

The End

Thanks for checking out my project!