The Ultimate Bike Odometer
The most goated bike tracker / bike speed teller you've ever seen. Built in GPS, LTE (Cellular), microSD card interface, 2x LCD, 1x E-ink, and all-round coolness. (ik this is glaze but trust. more detailed description here: https://github.com/roc-ket-cod-er/Cpeedo-mk5)
Created by
Madhav ๐
Tier 1
119 views
6 followers
Madhav ๐
added to the journal ago
Fixed up MQTT Edge Cases
So I woke up to see my board glitching out angrily. Hmmmm when did it go down? To check, I'd have to use the internet, but for some reason the internet was out... Hmmmmmmmmmmm
The internet comes back
Yipeee!
Now what...
Oh yeah when did it stop working. Looking at the data, it seems that it was working normally until 9:30

And I took a look at it about 15 mins later. (When it was glitching)
Hmmmm.... anything else that might have went wrong...
I now know why the wifi went out: it was because someone knocked out the plug and caused it to go down, about 5~20 mins before I saw it glitching out...
The good thing is, my IDE doesn't require wifi or anything, (it's literally just thonny), so I fired it up and watched what my program outputted as it spiraled into chaos.
Well it was OSError: -212. Not awfully helpful.
The biggest thing is that I wasn't able to catch it. The library, umqtt.robust actually auto catches the error and just tries again. The thing is, as its not connected to the internet it will end up never working.
Once I found that part out, it was quite the easy fix.

Since wifi.aconnect already returns True/False whether or not it's connected, I can then use that to change what happens.
That's one edge case down! After some more testing, I came across another edge case.
Edge Case #2 Incorrect Password
There is also this other thing that can happen, if by some chance you happen to come across another ssid with the same name or if you have the wrong password. In this case it will just end up hanging forever and that will really not be good, so instead I've made it have a timeout option.
And actually I think that's about all I did...
It took me an hour and a half for that 
I'm actually gonna log 0.1hrs though because I think it definitely seems like I've been inflating hours, but that's just my journal quality becoming lower. I really don't know why my thoughts just aren't that clear ig...
Madhav ๐
added to the journal ago
Implemented MQTT Library, learned about QOS
MQTT
Firstly, I worked on implementing the new MQTT library in, but there's this problem.
While updating, it wakes the Cell chip up to tell it the battery stats, the percentage and voltage.
However, the problem is, now that the majority of the updates will be on wifi I have to figure something new out. My solution to this was originally just to wake up the SIM7080G once every few cycles to update the servers with new battery information, but then I noticed that it only takes a few additional seconds so I decided to undo all my work and just replace it with some really simple code.
Here's my entire tracking script now, and you'll notice how it's pretty much nothing.
# ---------------- TRACKING CODE STARTS HERE ----------------- #
async def track():
try:
freq(80_000_000)
st = ticks_ms()
while ticks_ms() < 25_000 + st:
gps.update(4096)
if gps.sats[0] > 5:
break
print(gps.sats)
sleep_ms(20)
while ticks_ms() < 40_000 + st:
sleep_ms(20)
gps.update(4096)
if gps.lock:
break
gps.ban_updates()
if gps.lock:
gps.off()
cell.on()
bat_list = cell.at('CBC').split(',')
await mqtt.amsg(
f"{gps.pos[1][0]}",
"latitude"
)
await mqtt.amsg(
f"{gps.pos[0][0]}",
"longitude"
)
await mqtt.amsg(
f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s",
feed='other-info'
)
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
except Exception as e:
print(e)
shut_down()
except KeyboardInterrupt:
return
shut_down()
The important part is that I replaced all those weird checks and functions with the new MQTT library and now all that is requires is just the awaiting of the mqtt.amsg() function.
The rest is just getting a lock and checks.

^^ you'll see it still works ^^
STM32 Code Update
I just ran a simple code update to quickly make it so that the code gives the update function 90s now and so that the STM32 just resets the ESP32 if it is updating to make sure the user doesn't have to wait anything more than necessary.
QOS
I spent some time researching what QOS really does in the MQTT context, and as it turns out giving a qos of one makes the function blocking and ensures that the server receives the message. Thus, I made it so that it would no longer await for 1500 ms arbitrarily, but now it will just set QOS=1 and so it will just block that script until the message is successfully sent for the important stuff, and for the not-important runtime stuff it won't bother blocking and stuff. (Especially because the SIM7080G just has its own methodology and stuff and that will be used 99.9% of the time.)
Madhav ๐
added to the journal ago
MQTT Library
So I pretty much finished the work and testing of the publish side of the MQTT library, which basically provides an integration between both the wifi and cell layers, and this required a bit of change to those libraries, plus making the entirety of the MQTT library.
main.py isn't yet having the integration, that's a job for tomorrow.
Basically, I just made the mqtt library take in either a wifi object, cell object, or both.
It has some code in the __init__ function that actually took me quite a while to get the logic straight, but in the end it's quite nice. It has several custom errors it will through at you if you give it the wrong stuff. It is pretty much designed just to work with my libraries, but if someone wanted to they could of course write their own, they'd just have to make sure it passes my checks.
The init() function
Let's start off with it, here it is:
def __init__(self, cell=None, wifi=None):
if cell != None:
try:
if cell._version[0] == 1:
self.cell = cell
self.cell_setup = True
else:
raise RuntimeError("Incorrect cell object version.")
except AttributeError:
raise RuntimeError("Incorrect wifi object given. Required module is availible at 'https://github.com/roc-ket-cod-er/Cpeedo-mk5/blob/main/Firmware/ESP32/sim7080g.py'")
else:
self.cell = None
self.cell_setup = False
if wifi != None:
try:
if wifi._version[0] == 1:
self.wifi = wifi
self.wifi_setup = True
else:
raise RuntimeError(f"Incorrect wifi object version: {wifi._version[0]}")
except AttributeError:
raise RuntimeError("Incorrect wifi object given. Required module is availible at 'https://github.com/roc-ket-cod-er/Cpeedo-mk5/blob/main/Firmware/ESP32/wifi.py'")
else:
self.wifi = None
self.wifi_setup = False
if self.wifi_setup:
pass
elif self.cell_setup:
pass
else:
raise ValueError("No wifi object or cell object given.")
Basically, it starts off and checks if a cell object is given. If it is, it tries to check if library has a correct version attribute, and if so continues. If not, there are two possible errors it can throw: both RuntimeErrors, but one is if the library version is wrong, and the other is if you passed a completely wrong library which doesn't even have the ._version attribute.
A note here, for some stupid reason I have this code here:
@property
def version(self):
return "v1.0.0"
@property
def _version(self):
return [1, 0, 0]
As its in both libraries, it's pretty straight forward, however you'll not that I have the .version() as a readable attribute, and the ._version() isn't. For some reason in early debugging I had the .version() for both and I was really confused why it was giving me the 'wrong version' error. Hey, at least I can confirm it works!
Anyways, if there is no cell object, a perfectly possible scenario, it takes note of that and continues.
Then, it repeats the exact same steps for the wifi module. In fact, I wrote the wifi module by hand, then copied it for the cell one, but had to change so many things that it probably wasn't even worth it.
Next, it checks if at least one (either wifi or cell) is available, and if not raises another error.
And that's it for the init() code! It took me so incredibly long to get it to work and stuff, but now that I explain it it sounds so simple. The testing code wasn't the most cooperative either because you can't await outside of a function and unlike normal python's asnycio, micropython's uasyncio uses a different set where run() runs until complete, (so does run_until_complete()) and you need to run create_task() to run a new function asynchronously. Then I forgot to make the main loop async to actually let it run and all ... it was just a mess.
Anyways, for the messaging there is some simple code, just two individual functions wrapped in with a main one.
async def _wifimsg(self, msg, feed, qos):
if self.wifi_setup:
if self.wifi.is_on:
started = 'on'
else:
started = 'off'
self.wifi.on()
if not self.wifi.is_connected_to_wifi:
await self.wifi.aconnect()
try:
print(msg)
self.wifi.msg(msg, feed, qos)
except AttributeError:
self.wifi.connect_to_mqtt()
self.wifi.msg(msg, feed, qos)
await asyncio.sleep_ms(1500)
if started == 'off':
self.wifi.off()
return True
async def _cellmsg(self, msg, feed):
if not self.cell_setup:
return False
if self.cell.is_off:
await self.cell.aon()
started='off'
else:
started='on'
await self.cell.aconnect('io')
await self.cell.amsg(msg, feed)
return True
async def amsg(self, msg, feed, prefer="wifi", qos=0):
if prefer.lower() == "wifi":
if await self._wifimsg(msg, feed, qos):
return True
return await self._cellmsg(msg, feed)
else:
if await self._cellmsg(msg, feed):
return True
return await self._wifimsg(msg, feed, qos)
I actually spent a bunch of time trying to find out if there is a way to see if the wifi is busy or not, but that's really hard and needs qos=1, and that takes longer or something. All I know is that I didn't get a chance to test it out. Maybe a tomorrow thing...
Anyways, at first I just had one main function (this was when I only had the wifi going) but then when I wanted to add the prefer tag and have it fall back on the other I felt that two different functions were just far easier.
So, I split them up, and I think it works quite well as it is.

Obviously, I started off testing the wifi part first, and there was this thing where I just couldn't figure out how to get the coroutines and stuff to start up, but eventually I was able to by making a main() function, just for testing. Alas, it's more work than ideal, but I guess all's well that ends well...
There was this thing though that it would just end up creating a new test feed because all the publishing coroutines would run at a very similar time to each other and that just ended up making me go over my feed limit, but making it run less at the same time semi-fixed that.
Next up,
AttributeError: 'NoneType' object has no attribute 'write'
This one really stumped me.
So here's what was going on. I was running the umqtt.robust script which actually just ends up running the umqtt.simple script, but either way it was so weird how the thing gave that error.
Actually, this had also happened before and I never understood why.
As it turns out, its caused by not being connected to a MQTT server, thus wrapping it into a try: except: block can make it that if it fails then you know that it's not connected and just connect and rerun!
Simple solutions just sometime evade you... just if this stuff were easy it wouldn't have took me so gosh darned long ...
Alright well in total this took about 25 minutes to journal. Time really does fly doesn't it...
Madhav ๐
added to the journal ago
WEB DEV!
Now, you may be wondering why I'm doing web dev.
And so would I.
More seriously, the thing is I want to be able to use this odo for people to track the bike's speed, and thus I made a dashboard that, when the bike is in race mode, will cause it to update.
Here's and example screenshot of what the dashboard looks like.

Mind you, the code is quite good for me, as this is the second time I have ever done Javascript coding.
In the beginning I got AI to make it, but It just couldn't get it to work, the stuff was just getting cut off, so I had to take things into my own hands and fix up the code. It's honestly embarrassing that a beginner could fix the code that GPT couldn't.
Alongside the dashboard comes a new python script that you'll see labelled as race.py
The great thing is that it can also be used for measuring pretty much anything's speed, so this will also be used as our car's speedo. (It's a competition, don't worry about it)
Just so it is clear, the majority of the javascript is mine, but the majority of the css/html is GPT.
Try out my dashboard yourself here for the latest or here for the version as of journaling
I would recommend that you span ctrl + '+' to make the speedo bigger and easier to see.
Even though I know that this is webdev, it is related and compared to the total project time is so insignificant I thought it was definitely a nice way to interface with my stuff, and could be made as an example if someone else wanted to make an interface too.
The majority of the time outside of coding/debugging was trying to get vercel to work, but eventually I just figured I can connect my github and make vercel just rebuild every time a commit is pushed.
Note: to see the version that is as of journaling you might have to log in and even then it might not let you. I have tested the general dashboard to work for sure though.
Madhav ๐
added to the journal ago
[Retro] Wifi transmission (not quite good yet but works!
In case it isn't clear, this is a retro journal of my work on getting the wifi based MQTT updating started, but not yet fully functional.
I'll continue work once I finish this.
WIFIFIFIFI!!!
So yeah if you look at my work (that should be going up to github right about now), I've got some basic MQTT-ing going on, but as they are in individual libraries they aren't quite as useful as everything will be once I finish tonight, but I really had to get my data usage down, as 3~4ยข per day was adding up quickly.
So, I made some very messy code that would take care of that, but first I had to re-learn how to use MQTT. Thankfully some time ago I had already installed the web MQTT server thingy onto the ESP32 (idk when) and so it was there and ready to be used.
On the wifi side there is these functions, really stupid but they work, and yeah.
def connect_to_mqtt(self):
if self.client == None:
print("Failed to connect: No secrets file detected")
return "No secrets file detected"
try:
self.client.connect()
except Exception as e:
print(f"Failed to connect: {type(e).__name__} {e}")
def pub_lat(self, msg):
self.client.publish(self.latitude,
msg,
qos=0)
def pub_long(self, msg):
self.client.publish(self.longitude,
msg,
qos=0)
On the main.py side it's even uglier and it looks really stupid, and is as follows:
if w_connect:
print("wifi")
wifi.pub_lat(f"{gps.pos[1][0]}")
sleep_ms(6_000)
wifi.pub_long(f"{gps.pos[0][0]}")
sleep_ms(6_000)
bat_list = cell.at('CBC').split(',')
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
else:
bat_list = cell.at('CBC').split(',')
if gps.pos[1][0] != 0:
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
else:
cell.send_message(f"lat, {gps.pos[1][0]}, {gps.pos[0][0]}, {"/".join(map(str, gps.sats))}", feed='debug')
if gps.pos[0][0] != 0:
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
else:
cell.send_message(f"long {gps.pos[1][0]}, {gps.pos[0][0]}, {"/".join(map(str, gps.sats))}", feed='debug')
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
I seem to have fixed the issue as in I never get the debug calls anymore, so I can really clean up all the code.
And yeah that's pretty much what took me the majority of the time.
Really just getting the MQTT setup all working took the majority, but all in all I got everything done quite quickly.
Now what picture should i put...
oooh I have an idea... In the naming scheme I set it up so that the wifi client and the cell client have different names so they can theoretically publish at the same time!
Here's what an update looks like:

Madhav ๐
added to the journal ago
Started Adding Wifi Support & Optimised Boot Order
Let's get the quick one out of the way: I real quick adjusted the timing of things so that it wouldn't import the unnecessary things, and the TFT would be initialized earlier making it easier seen that the board turned on.
In addition, I cleaned up the main code with the addition of clear section labels. Hope over to my github to check them out, and here's a sample.
# -------------- Function Defines ---------------- #
# To shut everything down
def shut_down():
...
async def update_mqtt():
...
# ---------------- TRACKING CODE STARTS HERE ----------------- #
def track():

Wifi
So, I was looking at how much data this uses, and it's about 0.7mb per day or ~2ยข plus the daily connection fee of ~2ยข, so I was thinking of setting up something to just use wifi if any known network is around to transmit, and then creating a library that would just handle everything, and interface with a new Wifi library and my existing Sim7080G Cell library.
I started work from scratch, and setting it up to actually connect. As I already had a script to connect to wifi, I just simply started with that. As my plan was to try and make it just scan for networks first, then compare them to the list and see if any match, I'd actually have to make a scan function that wasn't blocking like the default one.
Eventually, I stumbled upon this solution.
Even though I don't particularly like how its running the stuff non-stop on the seconds thread (core (?)) it still gets the job done and I can await it so all's well on my side.
The thing is, I never like to use code that I have to deal with every day and isn't at least understandable to me.
So, the first thing I did was, naturally, unsuccessfully try to take it apart and make it more efficient. I would have liked for it to just fire off whenever I ask it to, but alas me not understanding the code made me not be able to edit it well.
Wifi connection.
Next, I worked on getting the wifi connection thing to actually just like connect to the strongest signal, but only try to connect if it actually had credentials for it.
Here's my code, and I'll explain it.
async def _connect(self, ssid, password):
self.wlan.connect(ssid, password)
await asyncio.sleep_ms(200)
while self.wlan.status() == STAT_CONNECTING:
await asyncio.sleep_ms(300)
if self.wlan.isconnected():
self.connected_to = ssid
return True
else:
print(WLAN.status)
return False
async def connect(self, force=False):
if self.wlan.isconnected() and not force:
return True
elif force:
self.off()
await asyncio.sleep_ms(50)
self.on()
if self.no_info:
return await self._connect(self.ssid, self.password)
else:
visible_networks = []
info = await self.scan()
for network in info:
visible_networks.append(network[0].decode())
print(visible_networks)
for network in self.wifi_info:
if network in visible_networks:
if await self._connect(network, self.wifi_info[network]):
return True
return False
Basically, in the core essence it will return True if it connects successfully, and False if not.
Firstly, like many things I code, it will check whether or not the user wants to force it to connect even if it already is.
This will cause it to, if forced, turn off the wifi and turn it back on. Next up I'll check if there is only one ssid/password (and as of now it needs to be provided at init or using force_new_info()) and if so it will just asynchronously connect to the wifi.
Otherwise, if the data is provided in a list, then it will asynchronously scan for available wifis, and then check which ones you have credentials for.
If there are multiple, then we want it to connect to the one with the best signal, obviously, and luckily, the data we get from .scan() is already sorted in that order!
Thus, we just go checking one by one and connect to the first one we can!
Wifi.py!
from time import ticks_ms, sleep_ms
from network import WLAN, STA_IF, STAT_CONNECTING, STAT_GOT_IP
import _thread
import uasyncio as asyncio
class Wifi(object):
def __init__(self, ssid=None, password=None, on=False):
try:
from secrets import wifi_info
self.no_info=False
except ImportError as e:
self.no_info=True
self.wlan = WLAN(STA_IF)
self.wlan.active(on)
self._scan_start = _thread.allocate_lock()
self._scan_start.acquire()
self._scan_complete = asyncio.ThreadSafeFlag()
self._scan_thread = _thread.start_new_thread(self._scan, ())
self._scan_results = None
self.wifi_info = wifi_info
self._connected_to = None
if self.no_info:
if ssid != None:
self.ssid = ssid
else:
raise ValueError("No SSID Provided")
if password != None:
self.password = password
else:
raise ValueError("No Password Provided")
def on(self):
self.wlan.active(True)
def off(self):
self.wlan.active(False)
self._connected_to=None
def _scan(self):
while True:
self._scan_start.acquire()
try:
self._scan_results = self.wlan.scan()
except Exception as exc:
print("Exception running scan", exc)
self._scan_results = []
self._scan_complete.set()
async def scan(self, force=True):
starting_on=True
if not self.wlan.active():
starting_on=False
if force:
self.wlan.active(True)
else:
return False
self._scan_start.release()
await self._scan_complete.wait()
self._scan_complete.clear()
if not starting_on:
self.wlan.active(False)
return self._scan_results
async def _connect(self, ssid, password):
self.wlan.connect(ssid, password)
await asyncio.sleep_ms(200)
while self.wlan.status() == STAT_CONNECTING:
await asyncio.sleep_ms(300)
if self.wlan.isconnected():
self._connected_to = ssid
return True
else:
print(WLAN.status)
return False
async def connect(self, force=False):
if self.wlan.isconnected() and not force:
return True
elif force:
self.off()
await asyncio.sleep_ms(50)
self.on()
if self.no_info:
return await self._connect(self.ssid, self.password)
else:
visible_networks = []
info = await self.scan()
for network in info:
visible_networks.append(network[0].decode())
print(visible_networks)
for network in self.wifi_info:
if network in visible_networks:
if await self._connect(network, self.wifi_info[network]):
return True
return False
def force_new_info(self, ssid, password):
self.no_info = True
self.ssid = ssid
self.password = password
@property
def is_connected(self):
return self.wlan.isconnected()
@property
def connected_to(self):
return self._connected_to if self.wlan.isconnected() else None
async def test():
wifi = Wifi()
print(f"\n\n{await wifi.connect(force=True)}")
print(wifi.connected_to)
wifi.off()
if __name__ == '__main__':
asyncio.run(test())
Madhav ๐
added to the journal ago
the line that i fixed
In case it wasn't clear or anything, here's what it was.
In the checking for the touch, now it's just:
if (esp_on) {
whereas is was
if (HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_8) == GPIO_PIN_RESET && esp_on) {

Madhav ๐
added to the journal ago
spent two hours fixing a problem that required just one line
Oooooh 60/60 chars!!!
I really do hate that limit though.
So, here's the problem.
As it is right now, the other things in the loop just block the touch controller for too long an so it doesn't have enough time to detect the interrupt and do its stuff (This was because it wasn't set up as an interrupt, but rather as a normal GPIO pin). So, what are my possible solutions?
Immediately, I thought of the SPI, and how if I used SPI DMA it would (almost certainly) be alright. Just one problem: IT IS NOT, IN ANY WHICH WAY, EASY.
In hindsight, there were also some other solutions I had, such as:
- Using DMA/Interrupt for I2C
- Stop looking for the interrupt.
SPI DMA
So I decided I'm not going to use GPT, so you'll see I haven't even opened it today.

(it's the 25th, btw)
But, anyways, I just thought of setting it up so that the function would use DMA and just work.
The function is pretty much just the same thing, but for whatever reason, it just wasn't very easy. (I never got it to work :( ...)
Anyways, the thing is firstly you have to set up the GPDMA and that has no documentation whatsoever for SPI use, and so I pretty much just winged it. I forgot to click 'Generate Code' after configuring the RX buffer, so that definitely could have been why my first try didn't work. But, I just went a long and set it up and compiled it. The biggest problem is that this stuff compiles properly, but then crashes at run time.
So, I was stuck there just wondering why it wasn't working.
All I was getting was straight 0s.
So, feeling beaten, I decided that I'm going to restart the DMA stuff.
This time, just following whatever research I could find, I got further. This time I had made sure to set both buffers up, and I used the RX/TX function, and not just the TX function.
When I opened the ESP32 code and ran it, this time I saw real numbers, 72 ...
The first number corresponded with 'H' so I thought all was well. What I didn't realize then was the fact that it didn't have the next ones, and instead was just straight up 72 72 72 72 72.
The biggest problem here, however, was the fact that it was just giving me 255, 255, 255, 255, 255 whenever I touched the screen, and then it would just straight up stop working and give me 0s.
I decided I was going to take the problem one step at a time, so I went ahead and thought maybe it was having a problem with the buffer not being how it liked, so I made a copy of it using (a new function to me) memset()
This didn't help at all, but oh well I guess there isn't much I can do.
OH SHOOOOT
I think I just realized why it wasn't working. I think for whatever reason it can only send one byte of data in the way that I have it set up, and thus that's why I was getting 72 or 255. In a normal 'Hello' packet, I send 72 and then 'e' 'l' 'l' 'o' and in a normal data packet I send '255' and then the data.
What if it was actually working the entire time and I didn't notice it... That would be crazy...
The 255s did look a bit off because I hadn't programmed it to do that, but I just thought that the SPI transmit pin was just getting stuck high.
Hmmmmm..... Oh well a problem for later. It works as is so...
Well, my solution was just as simple as to always read the position. This way if my finger was up, I'd have touch.touchPoints[0].state equaling false and if it was touching, it would be true.
This way, I would just update it every time the loop came around!
And I can confirm, it does work.
Now for my data, if it starts with a zero its the point where my finger lifted off, and if it starts with a 255 its where my finger is right now!
Corruption
So I was having this really weird problem where my stuff would get corrupted, and somehow increasing the baud rate to 1 MHz seemed to fix it. I also made a bunch of checks to see if it was ok, and here's my final code on the ESP32 side! Getting all these checks and timing an stuff did take longer than I'd admit, as it really is just a fine-tuning exercise.
CODE!
def read_bytes():
tx_dummy = bytes([255]*5) # send dummy byte (0xFF)
rx = bytearray(5)
spi.write_readinto(tx_dummy, rx)
return list(rx)
lv = []
while True:
value = read_bytes()
if value[2] == 0 and (value[0] == 255 or value[0] == 0) and value[4] < 2 and value[1] != 0 and value[3] != 0:
if value != lv:
print("Raw byte:", value, f" \tx: {value[1]:03d} y: {value[3] + 256*value[4]:03d} tstamp: {ticks_ms()%10000}")
lv = value
elif value == [0,0,0,0,0]:
pass
#print(value)
else:
pass
#print(value)
sleep_us(1000)
In terms of the STM32, I found that a timeout of 3ms worked best, as it was long enough that the ESP32 was reliably able to get the info clocked, but not too long as to block everything on the STM32.
Here's the relevant code.
if (esp_on) {
CST328_ReadData(&touch);
if (touch.touchPoints[0].state) {
print("X: %03d Y: %03d\n", touch.touchPoints[0].x, touch.touchPoints[0].y);
tx_buf[0] = 255;
tx_buf[1] = touch.touchPoints[0].x % 256;
tx_buf[2] = touch.touchPoints[0].x-256 > 255;
tx_buf[3] = touch.touchPoints[0].y % 256;
tx_buf[4] = touch.touchPoints[0].y > 255;
} else {
tx_buf[0] = 0;
}
}
Madhav ๐
added to the journal ago
GOT SPI TO WORK FIRST TRY!!!! + spent a few hours vibecoding
Yep! Just real quick got gpt to vibecode me a simple script that sets up SPI on the ESP32, and sets it up for recieving. I also made it vibecode a script for the STM32 so that it just keeps sending "O" (capital 'o').
Believe it or not, it actually worked first try. The ESP32 just kept receiving "O"s.
This was nice and simple, though, so I decided I'm going to now set it up to send/receive "Hello" or something like that. Setting up the stuff was easy enough, but here's where my skills in C come in. Or the lack there of.
So sending the single character of "O" was simple enough, but sending a string required setting up an array and stuff, and for whatever reason going uint8_t tx_buf[5] = 'Hello'; doesn't work. You have to have uint8_t tx_buf[5] = "Hello";.
Spot the difference?
Yeah one has 's and the other has "s.
That is enough to make it through a random error that my brain just couldn't process. However, since I'd seen something that looked like this problem before, I decided to change it to the "s and somehow it just fixed.
There was, of course, another problem where my lack-of-c-skills came into play.
So, afaik, the & symbol can be used to 'point' to a variable or something, but it's only needed for single variables, such as uint8_t h = "h"; and not for arrays like I have. That just felt so counterintuitive that my brain just took so long to wrap itself around it, but in the end I sort of understand.
Now, before we go further, I'd just like to say it was getting quite late and my brain started stopping to work properly. This meant that I was starting to rely more and more on GPT and less and less on myself.
TLDR: GPT just kept on putting me through way too much, including like a few hundred hallucinations, and in the end I just coded it myself, for something like 20 minutes.
So, I just kept on doing whatever GPT told me, and eventually we got to a spot where I just wanted DMA. DMA doesn't require basically and CPU and it just works in the background. So, I went down its rabbit hole, never having actually initialized DMA, and it just never worked. Eventually, I just gave up, and just control z'd everything. However, the problem with STM32CubeIDE is that it only holds the memory for like 50 or so actions, and since I had way to many, I was only able to undo like 8/10ths and had to do the rest of it myself.
Once I got back to my working 'Hello', I just literally decided to change it so that the first byte would be 255 if there was a touch or 0 if no touch, then it would do the following for the next 4 bytes:
- X-coord modulo 256
- 1 if x-coord is higher than 255 or else 0.
- Y-coord modulo 256
- 1 if y-coord is higher than 255 or else 0.
What this basically did was just have two bytes to tell me what the number was. If it's less than 256, say 255, you'd have 255%256=255 and a zero. However, if you had a number higher than or equal to 256, you'd have something like 256%256=0 and then a 1. This way to reconstruct the number I just to the first one plus 256 if the second is equal to 1.
And, believe it or not, it just straight up worked. Now, of course, like all my work, it is quite glitchy and doesn't work as nicely as I'd like for it to, but hey at least I can (most of the time) get the x/y coordinates to the ESP32!
Code
Here's the code I ended up with for the ESP32:
from machine import SPI, Pin
from time import sleep_ms
spi = SPI(
1,
baudrate=100000, # start slow for testing
polarity=0, # must match STM32 CPOL Low
phase=0, # must match STM32 CPHA 1 Edge
bits=8,
firstbit=SPI.MSB,
sck=Pin(9),
mosi=Pin(18),
miso=Pin(8)
)
def read_bytes():
tx_dummy = bytes([255]*5) # send dummy byte (0xFF)
rx = bytearray(5)
spi.write_readinto(tx_dummy, rx)
return list(rx)
while True:
value = read_bytes()
print("Raw byte:", value, end="\t")
try:
print("As str:", "".join(map(chr, value)))
except:
print("Not printable")
sleep_ms(1)
It's quite simple, just setting up SPI to match the STM32, and then just reading bytes by sending 255s.
It then just prints the raw bytes, and if for whatever reason you can actually print the numbers as text, it will. This is just mainly residual code from before. The STM32 just default sends 'Hello' unless there has been a touch, so that's also something to keep in mind.
Example
This is just a sample screenshot from the output of the ESP32.

This can be decoded into:
- 255 means valid touch
- 201 means x-coord = 201
- 0 means x-coord is not increased by 256
- 39 means y = 39
- 1 means y needs to be increased by 256 to 295
Note for reviewer: This journal took about 27 minutes to complete. This journal is for yesterday's work.
Madhav ๐
added to the journal ago
Got ready to set up SPI Pins
I migrated the update pin from one of the SPI pins to just be the shutdown signal as during an update the shutdown signal is not processed.
To do that I had to swap it out everywhere, but also re-write the emergency shutdown code as otherwise it would just goof up.
New code:

Madhav ๐
added to the journal ago
Updated Github with detailed descriptions
I did exactly that, for everything adding descriptions, such as 'updated libraries' or 'added old main file'

characters characters characters character
Madhav ๐
added to the journal ago
New print function
I forgot to put this in my journal about debugging like 2 ago, but I also build a new print function.
Its pretty much just like c's printf, but customized to print over usb!
void print(char* text, ...) {
ux_device_stack_tasks_run();
char buffer[1024];
va_list args;
va_start(args, text);
vsnprintf(buffer, sizeof(buffer), text, args);
va_end(args);
if (clock_state == lwPWR) {
SystemClock_ConfigUSB();
}
int len = strlen(buffer);
int offset = 0;
while (offset < len) {
int chunk_size = (len - offset > 8) ? 8 : (len - offset);
USBD_CDC_ACM_Transmit((uint8_t*)buffer + offset, chunk_size, &sent);
offset += chunk_size;
ux_device_stack_tasks_run();
}
if (clock_state == lwPWR) {
SystemClock_Config();
}
}
I pretty much already counted the hours in that journal, fyi.

Madhav ๐
added to the journal ago
Got Touch To Work!
Yep, now the STM32 is able to detect the touch!
It took what felt like forever to work, even though it was only an hour and a bit, because of the fact that c is just so slow.
Not in terms of running โ it runs faster than python by like 30x or something iirc, but in terms of compilation, uploading, and then debugging.
I probably had to go through over 30 iterations, each one taking forever.
In addition, in the beginning I had it set that you can't even use it for the first 20 seconds as its updating the web servers (tracking) but now I have it setup far better as in it now just checks if its been over 5 minutes since last update, and at the beginning (during debugging) I had it set to think it had just updated.
So, at first no matter what I did it just would be init'd by the library, but as it turns out the library isn't very good so I lowk just vibecoded a new library (with its headers, ofc) and just got it to work.
I also made it so that it would only bother checking if the ESP32 (and thus the screen drivers) were powered on.
As gemi wemi (gemini) put it, the bytes are arranged as such:
"Byte 0 (06): This is the Touch State. 06 means "Finger Pressed," and 00 (your last line) means "Finger Lifted."
Byte 1 (0A) & Byte 3 (6A high nibble): These form the X coordinate.
Byte 2 (04) & Byte 3 (6A low nibble): These form the Y coordinate."
Likewise, when I was printing out the raw values (to debug working), I could see that the values were changing based upon where I touched it, so immediately, without even gemi wemi's input, I knew that it was working.
Anyways, after I got gemi do vibecode me the new stuff I guess it just works. hmmm ... neat!
For the picture, I present you:

Madhav ๐
added to the journal ago
Spent ~2 ยฝ hours debugging a 13ms delay.
So I had this really big problem that my loop was taking 13ms longer than I wanted to and it literally made negative sense.
I had print statements in every single thing that would run to tell me if it ran, and I even double triple checked the USB was working properly and I ended up just going through each and every little nook and tried to isolate the problem.
I was so confident that the main loop wasn't the problem as everything was accompanied by a print statement. I thought it was more likely for the USB to fail.
As it turns out, it was something in the main loop. It was in an if statement, and that's why I never suspected it until I started commenting out each block of code one by one.
So here's the culprit: CST328_IsTouched(&touch)
The thing is I had left off at this spot last time and hadn't got it to work so I was wondering why the interrupt handler was not working anymore. At first I thought I broke the cable (screen to board) but I ruled that out as once in a while it would detect the touch.
So (GPT was certain the the USB was failing)[https://chatgpt.com/share/69ebafb7-92b4-83ea-baad-32d8d47ba9c2] but I ruled that out by determining how long the write took, less than once ms, but GPT straight up wouldn't believe me so it kept saying the same thing. It really lowk is just stupid.
The other thing that GPT suggested was that it could have been a clock issue that it wasn't initializing properly, but I was able to rule that out by seeing that the time added wasn't constant, so when it should have took 300ms it took 312, 500 to 520, and 1000 took 1014.
If it were to have been a clock issue it would have been constant.
Eventually I just thought that screw this I might as well try turning everything in the loop except for this read funcion off, and it just worked somehow!
From there it was quite straight forward, I just checked each piece!

^^ It's working!! ^^
Next up, I'd like to get the position from I2C, as just like last time all I can do is just tell if a screen has been touched by the interrupt pin going down.
Madhav ๐
added to the journal ago
Fixed GPS Tracking issue.
As far as I can tell, for the past some ~30 hours I've been running it, and not once has it given me a '0' and so I can now be confident that its working properly!!!
It sure is nice to have finally fixed the problem.
Latest thing I changed is that I allowed it to read the entire buffer in the tracking loop, and that I've banned updating as it starts to send data.
Previously, I'd sometimes have a '0' in one but not the other, and this seems to have fixed it!

Madhav ๐
added to the journal ago
Got TOUCH TO DETECT!!!! &tracking
Here's the furthest I got to:

You can see that the stm32 is detecting that the interrupt pin is being pressed, so you know for sure that there is some activity going on!
There were just so many problems: this was almost as hard as getting the USB to work
Like that hard.
It firstly took so long for my to try to make a claude account because apparently its better as I was determined to vibecode it.
No good. I don't have a phone number. Fall back, gemini. It was lowk pretty useful, it got me through the debugging alright, I guess.
The biggest thing was that I had to:
- Use the right I2C bus
- JUST SCAN THE GOSH DARNED BUS
Yep, it was just a library problem. My chip and everything were working, it's probably something in the vibe-translated library.
Basically, we (yes, I count gemini as a person) went through the debugging process, first scanning the bus, seeing the wrong values, ensuring the thing was connected (it was) ensuring all was like working, I started to think I forgot the I2C pull-ups (I didn't) and finally I though that maybe I'll just paste my code, and remind it that I'm using hi2c2 not hi2c1. And that fixed it: the code it gave me assumed it was. Quick fix, and now I get a bajillion addresses.
I guess that's better than none!
So why is that? I guess that's because of the timing, because when I gave it a Gemini Generated Timing, it worked and detected! Now I was only getting the one address I wanted, and everything seemed to go swimmingly, except, and this is about where I am, the fact that it wouldn't connect.
I've tried bypassing the connection-checking software, and still nothing. All I can do now is hope that tomorrow fixes it!

As you saw though, I can at least use the interrupt pin to tell me when a touch occurs, and that might just be something. I could then tell the ESP32 that and we'd be balling!
Again, to not bore you and for the interest of time (and my forgetting-ness) I haven't quite been very deep on the points, but I assure you this is the gist!
Madhav ๐
added to the journal ago
Fixed Glitch, Async'd Code, MQTT and Safe Power, New Fonts.
Gods, the 60 char title limit is annoying.
Anyways, I spent even more time debugging the glitch (where the code just randomly halts in the beginning for whatever reason) and now I have serious reason to believe it's the GPS which is just sending a huge packet of info, which slows the parsing.
To fix this, I've added a huge buffer, which holds 4kb of data, and made the update calls more controlled. Now, only where it's acceptable, it will take a look at the first 128b of data, and then if it still has time it will keep looking at the next 128. This way when we get a huge dump of like 4kB or something, it won't just hang the loop for like a second or two.
Next, I really added a bunch of async-ing, as basically all that the AT commands are is just send message in <1ms, wait 10~30~3000 ms, and then read.
It's actually lowkey so cool how I can just have two functions that for my purpose run completely at the same time.
I have the main function call the update_mqtt() function, and this then runs pretty much at the same time and just updates the web server.


Basically, all I do is that I just tell it to run, and every time the main cycle awaits, then the connecttoio function works and it just runs the next command.
It would be faster to run it on the other core, but that's just going to add too much work and make it impossible to debug and stuff. (Speaking from experience)
Of course, like with all programming this wasn't easy or anything. Firstly, like last time, I really had to pinpoint the cause of the slow down, and once I had the solution wasn't apparent immediately. I really didn't understand that it would just work so well, and thus I tried looking for pretty much any other solution. I was really thinking of multi-threading it.
There was also this place where I didn't know that you can await midline, like here:
while "99,99" in await self.aat("CSQ"): # signal
This really did cause a hiccup, with it hanging for several seconds here sometimes.
Before I made my code all nice and tidy as it is right now, there were several issues with it, such as it firing off commands in different orders because of the variable orders and stuff.
I really had a mess with all the power on, battery fetching, and shut off codes all asynchronous, which could end up with them being fired independently. In the end, I decided to go for the solution with just having a seperate function that would just call and run the commands in order.
Awaiting
Getting that to work was also quite something as I may have mentioned before, but everything sort of went quite well other than the hiccup above.
It did require some messing with, and in the end I made just three asynchronous functions, aat() for at commands amsg() to send messages to the AT server and aconnect() to connect asynchronously.
aconnect:
async def aconnect(self, server, url='', force=False, show=True):
if not self.is_on:
await self.aon()
while "99,99" in await self.aat("CSQ"): # signal
await asyncio.sleep_ms(100)
mqtt = ['io', 'adafruit', 'mqtt']
web = ['https', "web", "http"]
if not force:
if server in mqtt:
if self.connected() == "mqtt":
return
elif server in web:
if self.connected() == "web":
return
else:
self.warn("huh?")
await self.aat('SMDISC') #DISCONNECT FROM ADAFRUIT IO
await self.aat('SHDISC') #DISCONNECT FROM HTTP
await self.aat('CGDCONT=1,"IP","simbase"')
if show:
await self.aat("CEREG?", show=True) #signal type
while "NO SERVICE" in self.at("CPSI?"):
'''self.at("CSQ")
self.at("CEREG?")'''
await asyncio.sleep_ms(100)
#self.at("AT+CGACT?") #active?
#self.at('AT+CGDCONT?') #ip & stuff
await self.aat('CGPADDR=1') #connect to wifi
await self.aat('CNACT=0,1', exWait=0.1)
await self.aat('CACID=0')
if server in mqtt:
await self.aat('SMCONF="URL","io.adafruit.com",1883')
await self.aat(f'SMCONF="CLIENTID","{secrets.client_id}"')
await self.aat(f'SMCONF="USERNAME","{secrets.user_name}"')
await self.aat(f'SMCONF="PASSWORD","{secrets.key}"')
await self.aat('SMCONN', 15)
if server in web:
await self.aat('SHCONF="BODYLEN",1024')
await self.aat('SHCONF="HEADERLEN",350')
await self.aat(f'SHCONF="URL","{url}"')
await self.aat('SHCONN')
if show:
print(self.is_connected, "connected, iirc")
Data Use
Since I've updated it to update the server its started to draw and alarming 0.5mb per day, which brings the per day cost to around 3.5ยข. Per year, that's about 10 dollars. Ideally, I'm going to find a way to really lower that...
Fonts
I also took a look at the script again and generated some new fonts so that everything would be far more legible.
I also made the battery percentage be green above 30% and red below.
Auto Shutdown
For when the battery is dying


Basically, if the INA226 detects battery voltage below 3200 mV, then the STM32 will force shutdown the ESP32 by turning it off if it is updating, then turning it back on in while telling it to turn off. This will force it to turn off the SIM7080g, and then it will tell the STM32 to turn it off.
Then, the STM32 will itself go into shutdown mode, which should draw less than a ยตA.
I can confirm, that it does work. I speedrun discharged one of my batteries while journaling so that I could test it out.
Y'know, the biggest reason I decided to actually do this was because I've now over discharged the same liion twice.
Madhav ๐
added to the journal ago
Worked on Getting Battery % and Voltage
So, I got it to work but it has a really weird jiggy thing in the beginning where it just takes a bunch of time.

The middle row is time taken to complete in milliseconds, and for some reason it spikes, but only in the very beginning. It doesn't happen anywhere else at all. I've tried debugging everything, and I just have no clue whatsoever what is going on. I'm just gonna have to accept it, I guess...
Here's what I've tried in terms of checking if there is a blocking function
- Checking asyncio is working properly
- Checking if its the LCDs
- Checking if the Cell library is blocking
And I still have no clue.
The good news is, however, that I got asyncio to work!
Now, every 45 seconds, the SIM7080G will wake up, check the battery percentage, and shut back down.
I just have to speedrun add the data to the screens later and it will be well!
I also added the timestamps working as in it will update it once every time the GPS updates (200ms) and 100ms after each update.
This way the time will never stray and it will appear like its updating every 0.1s.
Also, thanks to the way that the SIM7080g only wakes for a few seconds, the majority of the time when the shutdown is requested it can be fulfilled so fast that the STM32 just turns it back on again, thus I added a 500ms delay. It actually makes it feel way more natural.
Also, my code has so many comments that it doesn't feel like my own :)
Really nice to know what everything is doing, even if they are a bit over the top sometimes.
Here's my entire main.py code. For the other libraries you can check out the github, which is now updated quite frequently.
# Important Imports
from machine import Pin, UART, freq
# Prevent Automatic Shutdown / Comms
stm_com = Pin(43, Pin.OUT)
stm_com.off()
# Imports
from sim7080g import Cell
from gps import GPS
from time import sleep_ms, ticks_ms
import gc
import uasyncio as asyncio
# Check if boot was for tracking or for
track_pin = Pin(9, Pin.IN)
TRACK = True if track_pin.value() else False
# If running normally, import the screen driver
if not TRACK:
from screen import TFT
# Simpler swap-ins to help simplify typing
ms = "ms"
# initialize objects
cell = Cell()
gps = GPS()
# initialize IOs
shut_off_pin = Pin(0, Pin.IN, Pin.PULL_UP)
shut_off_pin2= Pin(44,Pin.IN, Pin.PULL_DOWN)
# initialize slow objects if not tracking
if not TRACK:
tft = TFT()
# Function Defines
# To shut everything down
def shut_down():
if cell.is_off:
sleep_ms(500)
else:
cell.off()
stm_com.on()
def track():
try:
freq(80_000_000)
st = ticks_ms()
while ticks_ms() < 15_000 + st:
if gps.sats[0] > 5:
break
print(gps.sats)
cell.connect('io')
while ticks_ms() < 40_000 + st:
if gps.lock:
break
bat_list = cell.at('CBC').split(',')
if gps.lock:
gps.off()
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
except Exception as e:
print(e)
shut_down()
except KeyboardInterrupt:
return
shut_down()
# Main
async def main():
# Track if requested
if TRACK:
track()
return
# Set up screens
tft.fill_all(tft.black)
tft.left.write(
tft.big_font,
b'KM/H',
10, 200,
tft.white,
tft.black)
# Starting Variables
old_speed = 978
last_gc_collect = 0
last_cell_on = 0
btry = None
sat_string = ''
# Clear GPS Buffer
gps.uart.read()
# Run loop
while True:
# Update GPS Values
gps.update()
# await to give the cell on/off functions a chance
await asyncio.sleep_ms(5)
# check if cell chip is on
if cell.is_on and btry==None:
btry = cell.btry
print(btry)
asyncio.create_task(cell.aoff())
if last_cell_on + 45_000 < ticks_ms():
asyncio.create_task(cell.aon())
btry = None
last_cell_on = ticks_ms()
# Check if shutdown signal is given
if not shut_off_pin.value() or shut_off_pin2.value():
shut_down()
# Collect GC if it is getting full
if ticks_ms() > last_gc_collect + 120_000:
print("COLLECTING GARBAGE")
gc.collect()
last_gc_collect = ticks_ms()
# Update screens if there is new GPS information
if gps.new:
# Don't let GPS values change while updating screens
gps.ban_updates()
# Get starting time to monitor time taken to update
s_t = ticks_ms()
# Add main speed
if round(old_speed) != round(gps.spd):
tft.left.write(
tft.speed_font,
f"{round(gps.spd):02d}",
5, 30,
tft.white,
tft.black
)
# Add screen decimal and hdop
if old_speed != gps.spd:
tft.left.text(
tft.font,
f"{round(gps.spd%1, 1)}"[2:],
210, 160,
tft.white,
tft.black
)
old_speed = round(gps.spd, 1)
time_stamp = ':'.join(map(str, gps.time))
tft.right.text(
tft.font,
time_stamp,
10, 10,
tft.white,
tft.black
)
# Re-allow updates
gps.allow_updates()
if sat_string != f"{gps.sats[0]}/{gps.sats[1]}/{gps.hdop:0>.1f}":
tft.right.text(
tft.font,
f"{gps.sats[0]}/{gps.sats[1]}/{gps.hdop:0>.1f}",
10, 48,
tft.sky,
tft.black,
)
sat_string = f"{gps.sats[0]}/{gps.sats[1]}/{gps.hdop:0>.1f}"
# Print info out
print(gps.time, gps.pos, gps.spd, gps.sats, gps.hdop, ticks_ms()-s_t, last_cell_on + 45_000 - ticks_ms())
if s_t + 100 < ticks_ms():
time_stamp = str(gps.time[0]) + ":" + str(gps.time[1]) + ":"
time_stamp += str(gps.time[2]+0.1)
tft.right.text(
tft.font,
time_stamp,
10, 10,
tft.white,
tft.black
)
s_t = ticks_ms() + 200
if __name__ == '__main__':
asyncio.run(main())
btw, debugging the super long blip in the beginning was quite painful, and I really tried to optimise the code to run as quickly as possible to get the 10hz update, but it didn't work out. The python library is just too slow.
I did increase the buffer size to 4kb though, if that might help...
Also, here are the async definitions incase you wanted to see.
async def aon(self, shush=False, wait_for_boot=False):
if self.is_on:
if not shush:
self.warn("already on")
return
self.pwrkey.value(1)
await asyncio.sleep_ms(1300)
self.pwrkey.value(0)
i=0
if wait_for_boot:
while i < 500:
if self.at("AT", wait=0.03, include='') == "AT\r\r\nOK\r\n":
break
await asyncio.sleep_ms(70)
i += 1
async def aoff(self, shush=False, hold=False):
if self.is_off:
if not shush:
self.warn("already off")
return
if hold:
sleep_ms(100)
self.pwrkey.value(1)
await asyncio.sleep_ms(1500)
self.pwrkey.value(0)
if hold:
sleep_ms(2000)
I might also add the AT one as it really would be nice to async it...
Madhav ๐
added to the journal ago
Worked On Power Saving
I did a bunch of experimentation, and here are the numbers for idling.
The ESP32 draws about 45mA, out of which 5mA can be saved by lowering the clock speed. (160MHz to 80MHz)
The GPS draws about 20~25mA which can almost fully be eliminated by putting it to sleep, really useful for the tracking, where after getting a lock we can turn it off.
the SIM7080G draws 35 to 45 mA idling, and literally no matter what I do it won't go down. I tried turning of the radio and stuff, but that's not of much use (just experimentally) and that saved about 5mA. I tried to turn on PSM (Power Save Mode) but it just wouldn't enable for whatever reason.
And that's pretty much my entire gain after spending two hours on it. Yipeeeeeeee.
I really did spend a long time researching about the power modes of the SIM7080G, as it draws some 80~100mA during transmission.
I also spent a bunch of time reading through the STM32U5 power optimization guide, and pretty much nothing out of it was really of much use.

I'm still shocked that its drawing ~4.6mA just sitting there.
Main Code
I also worked on developing the main code, so much so that I think it's all set, and I'm now just making the main file import the new code and run it.
I'm now going to try and get asyncio to work so that the SIM7080G only turns on when needed, so I can save the huge power draw!
Madhav ๐
added to the journal ago
Spent an Hour Looking At LVGL & STM32 USB & Tracking
Y'know, the limit for the characters in the title really is pretty sad...
Anyways, I spent like an hour trying to understand LVGL and see if my screen is supported, and the good news is that it is.
The bad news, however, is that I can't find a single doc online showing me how I use it or any other built in driver included with the library.
All the docs are for the C applications.
Anyways, next! (I know that I'm just skimming over it, but there really isn't much more to say other than I just spent an hour looking for some way to get it to work. I might get it to work, but still not easy)
C
I lowk am just thinking that I might just re-write my code to run with the arduino IDE as it should actually work and that way I can use the display with LVGL. I really don't want to rewrite my code again (not quite done, but almost for my 2nd iteration)
STM32 USB
For some reason, my STM32 is drawing a tonne of current, about 5mA, and I really need to get that down. Currently, it lasts for about 5 days or so per battery for just tracking, as the average power draw is 20mA, 5mA + 160 mA for ~25 seconds multiplied by 12 times an hour.
I might try to get the ESP32 to go on a diet and shut up a bit so that should save power, but I tried to get the USB to save power, and that was pretty much useless. I might try again later, but as it is right now the vast majority of the power is being drawn by other peripherals and stuff, so that's where I gotta save power. The biggest thing I see is the USB connection, but that might be a tough thing as I have to de-initialize it, and then re-initialize it and it just hangs there for some reason. It's really weird but it just doesn't want to follow my (chatGPT) commands.
GPT is probably just hallucinating, y'know.
STM32 Cube programmer also just hung for whatever reason:

Yeah, I think I also took care of tracking, but I also just worked on making it actually work so that its seeing if the long/latitude is equal to 0, not the list.
long_lock = self.position[0][0] != 0
lat_lock = self.position[1][0] != 0
I also fixed the issue that in the time that it took to transmit it would lose the lock or something occasionally, so I have added two additional functions banning the library from auto-updating whenever it is called, and then unbanning it.
def ban_updates(self):
self.allow_update=False
def allow_updates(self):
self.allow_update=True
and implementation:
def rq(self):
if self.allow_update:
self.update()
self.last_given_timestamp = self.gps.timestamp
NOTE: HOURS DECREASED BY 0.5 AS I SORT OF GOOFED UP ON THE PREVIOUS JOURNAL

Madhav ๐
added to the journal ago
testing 123
ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss i can journal???????????

Madhav ๐
submitted The Ultimate Bike Odometer for ship review ago
Madhav ๐
added to the journal ago
Cooked Up Micropython, Font Errors, Implementation
Ok, so first thing's first, I decided that I want micropython. Custom micropython. Thankfully for me, my driver's repo tells me exactly how to compile it.
Just don't worry about the fact that it is some 4 years old or smth and half the stuff doesn't work. The first thing we have to think about the fact that making the ESP-IDF branch isn't easy more. Thankfully, there is only like one more step.
But, before getting to any of that, I had to actually install WSL, or something, which allows me to run linux stuff on windows. This was a pain, as I wanted to set the password to a blank one, but for some reason it didn't show, and instead I had to then redo that, which required uninstalling, and for some reason the uninstall options just didn't show up in the apps folder.

Weird, eh? There should be the app that would then let me uninstall it.
Well, after figuring it out, it still didn't help with me being absolutely unable to build micropython.
The amount of times i've had to run:

Like its actually depressing. This entire thing has taken me over 6 hours (including some UI and pythoning) and its just absolutely killing me.
I once got it to build properly, but for whatever reason it just didn't enumerate and gave me a weird error saying something about the I2C failing and rebooting.
I don't really know what to say: I've tried both using the right version of IDF with the latest Micropython, but that's not compatible with the st7789 driver, and I've tried the older version of micropython but then ESP-IDF keeps failing. If you want to see all my stuff, take a look at the following chat-gpt conversations.
https://chatgpt.com/share/69e02d43-fab0-83ea-9b27-8630b958d6e7
https://chatgpt.com/share/69e02d6d-0760-83ea-87d5-00eaa9fc94e4
https://chatgpt.com/share/69e02d82-9840-83ea-be03-a4d5e1230b30
https://chatgpt.com/c/69dfeba4-845c-83ea-ab5d-1049f9765772
Reflashing Mishaps
So remember how I said that I messed up the compilation and it messed it up and stuff? Well thankfully, I had taken a back up of my files, but it lost all its organization, and thus gave me so much trouble.
But, before I could even get my good micropython on, I ran into the time-old problem of flashing the wrong file.
The thing is, my ESP32s3 has octals-psram, not normal spiram. However, I had taken the firmware file of the spiram one and I was wondering why it wasn't booting. Finally, in a mostly panicked state, I got the firmware from the official side and somehow that worked. I then went on to putting all the files back, and completely losing them later. Basically, I added the official firmware, got it to boot, and then promptly proceeded to re-flash the broken spiram file and pretty much going crazy.
uggggggggggggggggggggggghhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
I really wanted it to work before I submitted it :(
Current Micropython Building
Ok, so just leaving that all out, I'm going to try building the micropython as the LVGL directory says, and see what happens...
I found a driver for the LVGL and lucky me, even though I can't find the spot to put the driver files, after a bit of exploring it looks like they are already there!

Time to build...
Huh, they have my ESP32-S3 there!
Time to install esp-idf for the bajillionth time!
IT WORKED!!!!!
JUST FOLLOWED IT AND IT JUST WORKED

(this alone took me over an hour btw (it was at the same time as some miner ui tweaks))
getting the usb to find and flash was a bit of a hassle, but it actually worked!!!
(I unflashed it now to use my old code again though)
UI and Other Micropython Stuff
So, I spent a good chunk of time trying to display text. If you recall, some time ago I made custom fonts so that I can have really big text.
Well, guess what!
I completely forgot that I have a very limited character set. How limited? Just the following characters:
'K' 'M' '/' 'H'
So, when I try to put, for example, 'hi', nothing shows up.
At first, I thought it was a screen thing. Maybe I'd not initialized the screen properly or the coordinates at which it was displaying were wrong. No worries, I tried putting the km/h text from underneath the number to the other screen. And for some god-forsaken reason it worked. The km/h displayed. I was like wth the code is the literal same, why isn't it showing anything???
Debugging: maybe it needed to have a slash? So I try the string 'h/i' and, as you, an all knowing overlord, would know, it worked. But only the '/'
I was full on going weird alarms, but then my not-very-good-but-sometimes-useful ai assistant helped me out and told me to look at the font.
And here lied the problem (as I already said)

Well yeah, quick fix just change the font to one that actually had full support.
I then programmed it to have special stuff so that it would only update the screen if the speed actually changed, and at first I just had a function on the inside, that would just check if the last speed value given was different than the current one and return True if that were the case.
Just one small problem, at the very beginning of the loop I was requesting the .spd attribute, so there was never a change from the last call (at the beginning of the loop) and the display call.
I moved the print down, but as it just didn't sit right I changed the code to the following:
while True:
gps.update()
if gps.new:
if old_speed != round(gps.spd):
tft.left.write(
tft.speed_font,
f"{round(gps.spd):02d}",
10, 30,
tft.white,
tft.black
)
print("spd")
old_speed = round(gps.spd)
tft.right.text(
tft.font,
str(gps.spd)+" ",
10, 30,
tft.white,
tft.black
)
print(gps.time, gps.pos, gps.spd, gps.sats)
I also added speed delta functions that would tell you the difference between the last speed request and the current speed.
@property
def delta_speed(self):
last = self.last_speed
return self.speed-last
@property
def delta_spd(self):
return self.delta_speed
They aren't being used as of now.
I also fine tuned where the decimal speed is now. (And of course added it in the first place) It still does feel weird though.
NOTE
y'know it is kinda sad that I'm going to be submitting today, and well yeah uh I would absolutely love to continue to work on this, maybe for some other ysws or just on my free time. I'm thinking that I will just continue to journal my time over on a google doc and you (the reviewer) can chose to add those hours. Please give me a note if you do so that I don't double dip. I really do want to finish with it working tho... GOOGLE DOC: https://docs.google.com/document/d/1c-lNGmR0YAwbqmByu39EthkbhZPvO9BD816Yln8PGi8/edit?usp=sharing
Madhav ๐
added to the journal ago
TFT Stuff
I spent the last half-hour just tuning and finding a good color for the foreground and background, and I settled on 0xEE55 (rgb565) for the front and rgb(8,0,8) for the back.
I also worked on the tft implementation, and here's the code!
import st7789
import tft_config
import tft.tfont2 as big_font
import tft.tfont1 as speed_font
import vga2_bold_16x32 as font
import vga1_8x16 as small_font
class TFT(object):
def __init__(self):
self.right = tft_config.config1(2)
self.left = tft_config.config(2)
self.right.init()
self.left.init()
self.speed_font = speed_font
self.big_font = big_font
self.font = font
self.small_font = small_font
self.st7789 = st7789
def fill_all(self, color=0):
self.left.fill(color)
self.right.fill(color)
@property
def black(self):
return st7789.BLACK
@property
def white(self):
return 0xEE55
@property
def sky(self):
return 0x867E
Well yeah, that sure did take a long time for the color, but it looks quite nice, it's a deep violet that is pretty hard to notice, but just way more soothing than plain black, and the foreground is a slight yellow, that just looks so nice.
I really also had to mess with the code as its name was tft, and it conflicted with something else and just failed. Then there was the problem of the entire library itself, but it just didn't sit right and I might just fully scrap it. Hmmmmmm....

the light yellow ^^
Madhav ๐
added to the journal ago
Finalized Tracking Code; Started Normal Run Code
Yep, I just did that.
I just updated the message statements to be:
if gps.lock:
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {ticks_ms()-st//1000}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
Basically, it now also shows the satellites in view, and separates them by a '/'
I also made the entire thing time based so that it will not be unpredictable, and it will always take a max of 30 secs for the first part, 10 more for the second.
I also put all that in a proper try/except block.
In addition, I also programmed the STM32 to now give it more slack, with 70 seconds to update, and updates once every 5 minutes. Should give me better data for now.
def track():
try:
st = ticks_ms()
cell.connect('io')
while ticks_ms() < 30_000 + st:
if gps.sats[0] > 5:
break
print(gps.sats)
while ticks_ms() < 40_000 + st:
if gps.lock:
break
bat_list = cell.at('CBC').split(',')
if gps.lock:
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {ticks_ms()-st//1000}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
except Exception as e:
print(e)
shut_down()
except KeyboardInterrupt:
return
shut_down()
uhhh what should i put for a picture...
well my battery is dying...

Time to swap it out.
Madhav ๐
added to the journal ago
Taking my tracker for a trip (1/2 hours counted)
So, to really test my tracker out, I took it out for a trip on the train!
Here's what it looks like, the big straight line being a place without good gps signal.


one is longitude, the other is latitude.
Madhav ๐
added to the journal ago
GUI
So, I want to have a nice little GUI, so I was thinking of using LVGL, as @sasha recommended.
The problem is, however, that I have to get it to work by building micropython for it, and after doing some research and stuff it seems possible, but it will be a pain to get both the LCD driver stuff and the LVGL stuff into the micropython file and actually build it and all that stuff, so I'm thinking of just going with the current setup and just rewriting the main file so far.

Madhav ๐
added to the journal ago
Fixed Old Code to Work Better
Basically, I adjusted the timing so that the esp32 would boot up and, as I'm in a big conctrete structure, if it doesn't see enough satelites it will just send battery data and tell me how many sats it sees (if any)
I pretty much just adjusted some timing on the ESP32 side of things so that it wouldn't get reset by the STM32.
Since I was using viper-ide.org, a far messier and slower IDE, it took forever to actually connect to the ESP32 as it had some bugs in the connection phase.
Definitely didn't help the the ESP32 was getting reset every few seconds.




I finally got some real photos!
Madhav ๐
added to the journal ago
New Tracking Code +
Yep, I got this sweet new tracking code that is soooooo much simpler and as I'm redoing everything much easier to use and see.
My code
is as follows:
# Important Imports
from machine import Pin, UART
# Prevent Automatic Shutdown / Comms
stm_com = Pin(43, Pin.OUT)
stm_com.off()
# Imports
from sim7080g import Cell
from gps import GPS
from time import sleep_ms
# Check if boot was for tracking or for
track_pin = Pin(9, Pin.IN)
TRACK = True# if track_pin.value() else False
# Simpler swap-ins to help simplify typing
ms = "ms"
# initialize objects
cell = Cell()
gps = GPS()
# Function Defines
def shut_down():
stm_com.on()
def track():
i = 0
cell.connect('io')
while i < 20_00:
if gps.sats[0] > 5:
break
sleep_ms(10)
i += 1
print(gps.sats)
while i < 40_00:
if gps.lock:
break
sleep_ms(10)
i += 1
if i < 40_00:
bat_list = cell.at('CBC').split(',')
cell.send_message(f"{gps.pos[1][0]}", feed='latitude')
cell.send_message(f"{gps.pos[0][0]}", feed='longitude')
cell.send_message(f"{gps.sats[0]}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) wait: {i//100}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {my_gps.satellites_in_use}s {my_gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
cell.off()
# Main
def main():
if TRACK:
track()
return
'''cell.send_message("i'm awake!")
cell.at("SMDISK")
cell.off()'''
while True:
gps.update()
if gps.new:
print(gps.time, gps.pos, gps.spd, gps.sats)
if __name__ == '__main__':
main()
Basically, I have this sick new code that should be waaaay easier as the main function will just run the tracking code if it detects that it needs to track itself, and otherwise just run normally.
If you are looking, you might see that newer, fancier gps code, as I'm addressing it as gps. But then you might look at this line and wonder: whaaaat?? The line in question: gps.gps.hdop
Why the funkyness?
Because, I developed
A New GPS Library
Ok, not quite. What I basically did was just create an inner layer that will basically take in commands, and then output them in a nice way, while giving me control to change something at every step of the way.
As its setup, here's my gps.py code:
from micropyGPS import MicropyGPS
from machine import UART
from time import sleep_ms
class GPS(object):
def __init__(self, uart_id=2, rx=5, tx=4, tmzone=-4):
self.uart = UART(uart_id, baudrate=9600, rx=rx, tx=tx)
self.gps = MicropyGPS(tmzone, 'dd')
self.cmd("$PMTK251,115200*1F")
sleep_ms(100)
self.uart = UART(uart_id, baudrate=115200, rx=rx, tx=tx)
self.cmd("$PMTK220,200*2C")
self.last_given_timestamp = []
def cmd(self, msg):
self.uart.write(f"{msg}\r\n")
def update(self):
if self.uart.any():
data = self.uart.read()
for char in data:
self.gps.update(chr(char))
@property
def time(self):
self.rq()
return self.gps.timestamp
@property
def pos(self):
self.rq()
return [self.gps.longitude, self.gps.latitude]
@property
def position(self):
return self.pos
@property
def speed(self):
self.rq()
return round(self.gps.speed[2], 2)
@property
def spd(self):
return self.speed
def rq(self):
self.update()
self.last_given_timestamp = self.gps.timestamp
@property
def new(self):
return self.gps.timestamp != self.last_given_timestamp
@property
def sats(self):
self.rq()
return [self.gps.satellites_in_use, self.gps.satellites_in_view]
@property
def satellites(self):
return self.sats
def use_sats(self):
return self.gps.satellites_in_use
def view_sats(self):
return self.gps.satellites_in_view
@property
def lock(self):
long_lock = self.position[0] != 0
lat_lock = self.position[1] != 0
return True if lat_lock and long_lock else False
Please note that every single character there was typed by hand, save a few copied @propertys and like one function that was copied.
Again, like with my last library, you will notice a few aliases, and again this is just so that stuff can be typed a lot easier. Key functions this has that the other one didn't include:
- Organised Position (returns a list, longitude then latitude)
-
satsfunction to tell you both the visible and connected satellites -
lockfunction to tell you if it has actual coordinates detected -
newfunction to tell you whether or not you have accessed anything from this timestamp -
rqfunction to notify the library that you are using data, only necessary when you are directly using the lower-level functions of the micropygps library. All implemented functions and read requests automatically call it.
You will also notice that there are some nice, juicy, comments!
This way any person who comes along later to take a look at my code will understand all that is going on here.
I also published all my ESP32 firmware to github!

Madhav ๐
added to the journal ago
Re-organised Files, Worked on a More Intuitive On/Off
Basically, I just, one at a time, copied the files and put them into new folders so that they are actually nice!
Here's what it looks like as of now.

One small quirk is that I have to import tft.font1 now as its in the tft folder.
Then, I worked on creating a better way to turn on/off the sim7080g, and here's what I came up with:
Basically, like last time, I'm still gonna have lots of alias functions, but now the main ones is just .on
The thing is, this created the need for me to free up the previous .on/.off functions, and now they are named .ison/.isoff, which required me to update every single reference.
Other than that, I think all is well now...
Code snippets:
@property
def is_on(self):
if self.pwr_detect.value():
return True
return False
@property
def is_off(self):
return not self.is_on
def power_on(self, shush=False, wait_for_boot=True):
if self.is_on:
if not shush:
self.warn("already on")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
i=0
if wait_for_boot:
while i < 2000:
if self.at("AT", wait=0.1, include='') == "AT\r\r\nOK\r\n":
break
i += 1
def pwr_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def turn_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def boot(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def off(self, shush=False, hold=True):
if hold:
sleep_ms(100)
if self.is_off:
if not shush:
self.warn("already off")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
if hold:
sleep_ms(2000)
def turn_off(self, shush=False, hold=True):
self.off(shush=False, hold=True)
def power_off(self, shush=False, hold=True):
self.off(shush=False, hold=True)
def shut_down(self, shush=False, hold=True):
self.off(shush=False, hold=True)
Btw I did some debugging for a future journal, and oh boy does the simpler version come in handy without the 'AT+'. (Such as cell.at("SMDISK"))
Madhav ๐
added to the journal ago
Made SIM7080G Library
Yep. Took me some three hours to debug this thing, but it's there now.
Look at how gorgeous this looks:

Of course, it wouldn't be calle test, but this was just a test so...
Anyways, here are the major hiccups I faced:
Major Hiccups
- New UI, Same Brain
- Weird Ordering
- Thonny Failing
- Secrets
In order:
New UI, Same Brain
The first and biggest thing is that my new thing is much simpler, and just much easier to use. However, my brain is used to the older way of thinking, so that really messes with me.
Now, you just say: self.at("SMSTATE?")
Before you'd have: send_at("AT+SMSTATE?")
I made the at command just .at and now it includes the mandatory AT+. This was something that really messed me up earlier, as I sometimes just typed the command, forgetting the AT+ and expected it to work.
However, now I had the problem that it just wouldn't be happy if I gave it the old one.
In addition, I now had it output the following cases:
(None, None): this is if it times out
(raw response, False): this is when it fails to decode it, but successfully reads.
(decoded response, True): this is the most common (99% of the time) when it successfully decodes the message
However, as I was occasionally lazy and just copied (and both adapted and optimised) a function. The problem was, it expected the answer to be just the answer, not the list. There just were sooo many places where it failed that even after finding every possible place, it still failed and I just gave up and undid it.
In addition, I also made my code to be that there were tonnes of options for the same function, but even then I'd still have a tendency to look back and see what it actually was, which was sort of stupid of me.

Weird Ordering
Here's a snippet of my code for context:
def connected(self):
resp = self.at("SMSTATE?")
if '+SMSTATE: 1' in resp:
return "mqtt"
resp = self.at("SHSTATE?")
if '+SHSTATE: 1' in resp:
resp = self.at("SHCONF?")
return "web"
return None
def connect(self, server, url='', force=False):
if not self.on:
self.power_on()
while "99,99" in self.at("CSQ"): # signal
sleep_ms(100)
mqtt = ['io', 'adafruit', 'mqtt']
web = ['https', "web", "http"]
if not force:
if server in mqtt:
if self.connected() == "mqtt":
return
elif server in web:
if self.connected() == "web":
As you can see, the connected() command requires for the modem to be active and responding, however sometime it isn't until it connects to the cell towers. Thus, you want the CSQ command to run first, as it will just stay there and deal with its problems until it gets a signal.
For some reason, in my previous function it was different as in it would call the connected function first, which would cause the entire thing to hand.
What I find most confusing is that my old code still works, reliably that too, but this one really kept failing some 50% of the time.
The biggest reason this could be is because I really tightened up some timings, removing extra waits and everything.
This really did take me a while for some reason, probably because it was a (mostly) copied part of it that I expected it would work.
Thonny Being A Big Dumb-Dumb-Bell
So yeah, as useful as Thonny is (my really basic IDE that does not support hackatime or any other extensions), there is this big problem that it kept. on. hallucinating.
No matter what I did.
I spent a solid chunk of my time, and thonny kept on saying that a line was calling on the time module, which I didn't import directly. (It was the sleep() function, but I had from time import sleep)
The thing was, that error was exactly correct, just for a problem that was TEN LINES DOWN
I literally questioned my python (and thus coding) skills several times during the debugging.
Last point: Secrets
This is just a feature I added, so that every time I publish to github I'm not giving away my secret information, I just made a secrets file that contains my IO key, my username and the device name.
My complete code.
Because you might be wondering what did I do in these three hours, take a look!
most of this was in fact handwritten, as the previous stuff was all junky-monkey in a weird way that really is shocking still works.
from machine import UART, Pin
from time import sleep_ms, ticks_ms
import secrets
class Cell(object):
def __init__(self, pwrkey=48, pwr_detect=15, init_uart=True):
self.uart_init = False
self.pwrkey = Pin(pwrkey, Pin.OUT)
self.pwrkey.value(0)
self.pwr_detect = Pin(pwr_detect, Pin.IN)
self.init_uart(1)
def warn(self, msg):
print("ERROR:", msg)
def init_uart(self, uart, baudrate=115200, tx=41, rx=42, cts=40, rts=39):
self.uart = UART(uart, baudrate=baudrate, tx=tx, rx=rx, cts=cts, rts=rts)
def cmd(self, cmd):
self.uart.write(cmd+"\r\n")
def at(self, cmd, wait=10, show=False, end="\n", exWait=0, search=False, include="AT+"):
resp = b''
found = False
start = ticks_ms()
self.uart.read()
self.cmd(include+cmd)
while ticks_ms() - start < wait * 1000:
if self.uart.any():
resp += self.uart.read()
if b'OK' in resp or b'ERROR' in resp:
found = True
break
elif search:
if search in resp:
found = True
break
sleep_ms(1)
sleep_ms(int(exWait*1000))
if self.uart.any():
resp += self.uart.read()
if show:
print(">>", cmd)
if resp:
try:
if show:
print("<<", resp.decode(), end=end)
return resp.decode()
except UnicodeError:
if show:
print("<< UNICODE ERROR", end="\t")
print("resp", end=end)
return resp
else:
if show:
print()
return None
@property
def on(self):
if self.pwr_detect.value():
return True
return False
@property
def off(self):
return not self.on
def power_on(self, shush=False, wait_for_boot=True):
if self.on:
if not shush:
self.warn("already on")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
i=0
if wait_for_boot:
while i < 2000:
if self.at("AT", wait=0.1, include='') == "AT\r\r\nOK\r\n":
break
i += 1
def pwr_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def turn_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def boot(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def power_off(self, shush=False, hold=True):
if hold:
sleep_ms(100)
if self.off:
if not shush:
self.warn("already off")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
if hold:
sleep_ms(2000)
def connected(self):
resp = self.at("SMSTATE?")
if '+SMSTATE: 1' in resp:
return "mqtt"
resp = self.at("SHSTATE?")
if '+SHSTATE: 1' in resp:
resp = self.at("SHCONF?")
return "web"
return None
def connect(self, server, url='', force=False):
if not self.on:
self.power_on()
while "99,99" in self.at("CSQ"): # signal
sleep_ms(100)
mqtt = ['io', 'adafruit', 'mqtt']
web = ['https', "web", "http"]
if not force:
if server in mqtt:
if self.connected() == "mqtt":
return
elif server in web:
if self.connected() == "web":
return
else:
self.warn("huh?")
self.at('SMDISC') #DISCONNECT FROM ADAFRUIT IO
self.at('SHDISC') #DISCONNECT FROM HTTP
self.at('CGDCONT=1,"IP","simbase"')
self.at("CEREG?", show=True) #signal type
while "NO SERVICE" in self.at("CPSI?"):
self.at("CSQ")
self.at("CEREG?")
sleep_ms(100)
#self.at("AT+CGACT?") #active?
#self.at('AT+CGDCONT?') #ip & stuff
self.at('CGPADDR=1') #connect to wifi
self.at('CNACT=0,1', exWait=0.1)
self.at('CACID=0')
if server in mqtt:
self.at('SMCONF="URL","io.adafruit.com",1883')
self.at(f'SMCONF="CLIENTID","{secrets.client_id}"')
self.at(f'SMCONF="USERNAME","{secrets.user_name}"')
self.at(f'SMCONF="PASSWORD","{secrets.key}"')
self.at('SMCONN', 10)
if server in web:
self.at('SHCONF="BODYLEN",1024')
self.at('SHCONF="HEADERLEN",350')
self.at(f'SHCONF="URL","{url}"')
self.at('SHCONN')
def send_message(self, msg, feed="test"):
self.connect('io')
self.at(f'SMPUB="space_coder/feeds/{feed}",{len(str(msg))},1,0', end=" ", search=">")
self.uart.write(str(msg))
start = ticks_ms()
resp = b''
found = False
while ticks_ms() - start < 5000:
sleep_ms(1)
if self.uart.any():
resp += self.uart.read()
if b'OK' in resp or b'ERROR' in resp:
found = True
break
try:
print(resp.decode())
except Exception as e:
print(e)
print(resp)
Madhav ๐
added to the journal ago
Tried To Get SD Card to work
I spent a few minutes finding a tutorial to get the SD Card to work, but it really felt like far more trouble than it was worth, with the tutorial videos being like 50 minutes long, plus my own debugging and understanding so I pretty much quit the second they asked for a middleware that I didn't have.

Madhav ๐
added to the journal ago
Got INA226 to work; used it to measure power draws.
Well, the great news is that the INA226 is working, but it is a bit redundant as the cell chip can already take care of basic voltage measuring and functioning as a fuel cell chip.
Anyways, somehow it was as simple as importing the library, and just importing it and using the example code!
There were definitely things I had to tweak, like, y'know the header files to be for the U5, and then there was also the fact that the I2C that got initialized was called hi2ci, and they expected it to be called something else, but all in all it was quite nice.
However, the biggest problems came after I got the library: the library compiled and everything just fine, however it wouldn't output anything. I'd just get zeros. Well, that's not good.
However, for whatever stupid reason, they way I had set it up was to print like this:

And this really triggered my brain.
The thing is, I had forgotten to turn the INA226 on.
Yep. The INA226 draws power from an STM32 IO, and since that was off, well of course it didn't work.
So, I turn it on and ...
It still doesn't work.
For whatever reason, I'm getting 255000 back from it. This is where I realize that I was giving it the wrong I2C handle (after accidentally pasting the code in the GPT) I fix that, ... and, ... nothing.
See, because the return value that the INA226 gives is a float, and my print function can't take floats, I had been converting it into an int. Thus,
My Code Story
My code looked like:
print((int)INA226_getBusV(&some-random-other-address, INA226_ADDRESS));
The thing is, that gives very low resolution, so I made it be
print((int)INA226_getBusV(&some-random-other-address, INA226_ADDRESS)*1000);
This was where I was getting 255000 from btw.
I remove that thousand to get 255
print((int)INA226_getBusV(&some-random-other-address, INA226_ADDRESS));
Then, considering it fixed, I went back and added the *1000 when I update the address.
print((int)INA226_getBusV(&hi2c1, INA226_ADDRESS*1000));
Notice anything wrong?
The *1000 is before the ')' and thus it multiplies the address, not the output.
So, I'm still getting 255s.
Eventually, I figure that out, fix it, and to my absolute shock and surprise, it works!
I'm getting outputs that are around the 3200 range ... but I measure my battery, and it reads ~3999 mV.
Huh, how weird.
Scalers
So the thing is, apparently the library just straight up forgot to put in the scalers, where the resolution for the voltage is *1.25, and the current is *2.5
Fixing those, and finally is all works.
I then spend the rest of my time just cleaning everything up, making it look nice, and just testing it out everywhere!
p.s. An example is the following functions. For a complete update, you can see the github, which should have a commit right about now.
int current_ua() {
return INA226_getShuntV(&hi2c1, INA226_ADDRESS) * 50;
}
int current_ma() {
return (int)(current_ua() / 1000);
}
Madhav ๐
added to the journal ago
Print Function & Clocks
So I quickly got a basic print function going, and then just vibecoded a more advanced one, so that now you can only send eight bites at a time and not overflow the machinery.
However, as the USB requires really high clocks, at least 28 MHz out of my testing, I set up and played around with the clocks until that they can be set to 0.5 MHz in run mode, and then 48 MHz in USB comms mode.
To test that out, I have my print function checking if the clock is the right speed, and if it isn't then it will increase the clock speed, send all the data, and then lower it back down again.

And here's the data, updating really slowly, just once a seconds, telling me if it is on or not. I'm going to change it to tell me the INA226's readings (that was the whole point anyways) and then I'm going to submit the project!
Madhav ๐
added to the journal ago
GOT USB TO WORK AFTER WHAT FEELS LIKE AGES!!!!!!!!!!!!!!!!!!
You see how I maxed the characters out?
I finally feel ready to submit.
Maybe I'll just add a print function, but otherwise I'm done. I'm actually done.
or i might just work on the i2c and other stuff too...

me using the Arduino IDE serial monitor 'cause I can
Anyways, I'm getting ahead of myself.
The Process.
So I followed that tutorial that I talked about last time, and I actually came to a problem in pretty much the same spot as some other tutorial I did.
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x00 , PCD_SNG_BUF, 0x20);//EP0 OUT
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x80 , PCD_SNG_BUF, 0x60);//EP0 IN
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x81 , PCD_SNG_BUF, 0xA0);//EP1 IN
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x82 , PCD_SNG_BUF, 0xE0);//EP2 IN
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x03 , PCD_SNG_BUF, 0xF0);//EP3 OUT
However, by some magic, I found the following replacement code and it somehow works!
Not the best, but it still does work. (I'll talk about this later)
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, USBD_MAX_EP0_SIZE / 4);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, USBD_CDCACM_EPIN_FS_MPS / 4);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, USBD_CDCACM_EPINCMD_FS_MPS / 4);
Anyways, I just kept on following the tutorial, even after this hiccup, as I was confident like I was last time (this would be ominous foreshadowing if it didn't work, BUT IT WORKS!!)
But yeah, I just kept following the tutorial, and there were a few places where I got a bit lost, especially near the end, and it didn't help that I can't spell receive(d) or pretty much any word with both an 'i' and 'e' one after the other (there were lots of those)
But yeah, it was a huge let-down when the USB didn't enumerate the first time, but I think there was some little code I messed up somewhere, and after fixing it it just worked.
IT JUST WORKED.
IT JUST WORKED!!
On a side note, I guess it really is true that the more effort you put into something the better it feels when it works...
Testing & 8-Bites
So yeah once everything got to work (~1h40) I then spent the remaining time trying everything in my power to fix the 8-Bite rule.
That, basically, is that the USB was only able to echo 8 characters, one of which a new line.
Awfully inadequate.
If I sent it 9 bites, then it would crash in a (not very) fiery display.
By debugging it, slowly, I was able to isolate the problems. At first, I thought it was the read buffer messing up, but I then, after finding no fix, went in and tried to just make the LED flash once for every bite it received, and believe it or not, the LED flashed at 9 bites.
Well, now that the problem was isolated, certainly a TX messup, I just had to solve it.
If only it were that easy, ofc.
It wasn't. And thus I have no solution. My best bet as of now is just to create a print function that will just make it print 8 bites at a time. Oh well... At least the USB works...
Learning C++ Arrays & springf
So, in the debugging process, I thought that I would make it just print a combination of the length it received and the message. i.e, 3: hi
However, apparently c doesn't have text, and thus I have to add arrays together, again not easy, and something like that. All I know is it works.
Same thing with the springf function. Apparently, its just like printing, except to a char array (the thing we call a string)
Also, look at the difference in complexity between C and Python:
python: response + ' ' + len(response) or f"{response} {len(response)}
c:
a bunch of gibberish no one understands that is ~6 lines long.
Well, time to go get the print function working!
Sasukeop159
gave kudos to The Ultimate Bike Odometer ago
fuckin awesome!!
Madhav ๐
added to the journal ago
Configured Clock to be more Accurate
Instead of using the internal HSI clock, I've now re-configured it to use the external 12MHz clock I put on it. This should really help with the time drift that it otherwise experiences of about ~5 seconds per 10 minutes.

Madhav ๐
added to the journal ago
Made New STM32 Files & Project
So basically, there were a few things bugging me, primarily not being able to undo a lot, the project name, and having all my messed up files from trying the USB things from yesterday.
Thus, to fix them all, I created a CUBEMX copy of the previous project, so it will copy the IO settings, but not the code. I put this all into my GitHub repo so I can then also change my stuff by going back to previous commits.
Lastly, by creating a new project, I naturally got the new name.
All that was left was to copy the correct code over, and now that that's done I should be all good!
Time to TryToGetThisUSBtoWork ยฉ 2026
Btw I also tested the code I put on it, and so far all is well!

Madhav ๐
added to the journal ago
Failed To Get STM32 USB Working v2
This time, instead of using USBX, I tried to get the legacy serial to work, and I found this link that has (supposedly) support for the u5 series (link highlights mention)
However, after following their steps word for word, I started getting errors about here:
It was [these lines](https://community.st.com/t5/stm32-mcus/how-to-use-stmicroelectronics-classic-usb-device-middleware-with/ta-p/599274#:~:text=HAL_PCDEx_PMAConfig((,0x140)%3B) that were failing:
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x40);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x80);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, 0xC0);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, 0x100);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD_EP , PCD_SNG_BUF, 0x140);
As I have no clue why this is happening, I consulted GPT. It got me to replace the code with something else as the STM32U5 series uses FIFO or smth for the USB and that has a different setup.
All in all, I got the errors to go away, but I have no clue why it didn't work in the end.
I kept getting:

Anyways, hoping for the best I continued on with following the article.
I got a bit confused over here:
#define USBD_VID 0x0483
#define USBD_PID 22336 /* Replace '0xaaaa' with your device product ID */
But it just turns out it was for nothing. I google what all that stuff means and I think it should be alright, but maybe that's why my USB isn't enumerating...
There were some places where I couldn't understand where the function had to go, but i was able to figure it out after some struggling.
However, after it all did its stuff, for some reason when building the file it just wouldn't work. Huh.
There were some problems, and thus I consulted GPT (a pretty stupid move). It sort of went into hallucination mode and just spit out random stuff that was most likely a bad idea to delete but removed the errors, so I just went along with it.
That still didn't solve the fact that it couldn't find some declarations, but I think that that was because I was running the other usb_desc.c file while it needed a variable in the main.c file or something. However, me not knowing that, I went in with GPT and tried pretty much everything to get it to find the variable, but failed. I don't really remember how I got it fixed or even if the problem was just running the side file.
Anyways, the point is I spent two and a half hours for yet another failed attempt :(
I was actually confident this time ): ): :( :(
Next Steps
Well, I have one last thing that I can try, and that is to get USBX to actually work, as it is the only one that is still natively supported by STM32CUBEMX, and thus I somehow managed to find this video: https://www.youtube.com/watch?v=43gcc2dGnxQ
If this works I guess that would be one heck of a way to go out: struggling to find something so obvious, just like the beginning (with the cell chip, in case (like me) you forgot.
AS I LOG MY 246th HOUR, I must really reflect upon how great of a journey this has been, from a dream some 6 months ago to fruition. (And pretty much everything I designed worked spectacularly!)
Madhav ๐
added to the journal ago
Code Improvements, Cleaned up feed so far.
Firstly, I just went into Adafruit IO and just cleaned up all the zero values, but just doing on feed took me so long that I just gave up and deleted the feeds.

All I have now are those 8 points or something.
In addition, I really fixed up my code so that it runs significantly faster, going from ~315ms per cycle to ~63 per with 1/5 being ~113.
In addition, I also fixed it up to catch more errors in 'except' blocks so that the code wouldn't just go and fail.

Two such hot spots

I also updated it to send the battery voltage to Adafruit IO, and well yeah. That's what took me some three hours!
The speed improvements really did take a bunch of time, as I had to move the garbage collector (gc) code so that it was better and only ran once every 2 minutes, in which case it would clear the GNSS UART queue so that nothing weird happened. Finding the gc code was actually quite a thing because I had to run tests isolating everything to see what was actually slowing it all down.
The other 50ms speed savings are by reducing the amounts of write commands called by simply just putting them all to run only once every seconds, because realistically that's all I need, and now when the time goes up everything else will also only update then.
I also played with some boot ordering, so now it will turn on the modem, wait for a gnss lock in which time the modem will (hopefully) connect to the internet, and then transmit.
power_on()
i = 0
while i < 3000:
while gnss.any():
data = gnss.read()
for byte in data:
stat = my_gps.update(chr(byte))
sleep(0.01)
print(my_gps.satellites_in_use)
i += 1
if my_gps.satellites_in_use > (3000-i)//300:
break
connect_to_cell('io')
bat_list = send_at('AT+CBC', show=False).split(',')
send_message(f"{my_gps.latitude[0]}", feed='latitude')
send_message(f"{my_gps.longitude[0]}", feed='longitude')
send_message(f"{my_gps.satellites_in_use}s {round(my_gps.speed[2], 2)}km/h {my_gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
sleep(0.5)
Madhav ๐
added to the journal ago
Gave GNSS More Time To Set Up
I update my code to read:

This means that if it connects to 10 sats it will signal immediately, and for every 3 seconds that pass it will require one less satelite.
btw I also tested this ofc and everything.
Madhav ๐
added to the journal ago
Failed To Get STM32 USB Working; INA226 Code
Ok well today was pretty much a total failure, as I spent all my time trying to get the INA226 to work, for which I needed to have USB communication or something to actually get the data off.
So, first things first, I looked through the libraries and really there only is one that can work, and I tried copying things out of it. I copied the #defines and some really basic functions, just the read voltage and read shunt-voltage. However, it gave me the error that it couldn't find an I2C bus: I hadn't set it up, and that it couldn't find the float32_t typedef. This one really through me off, but as it turns out it's pretty much just the same as the normal floats. This one really took me a bit to understand as it really was weird.
Anyways, all that stuff was practically heaven compared to what came next.
Pretty much the worst (almost) three hours of my life
No matter what I did, there is literally nothing I can do to get the STM32 to just initialize its USB port, and just talk to me.
Apparently, I need to set up USB CDC, but the biggest thing is that they really stopped making it easy to use the old libraries, and now you have to use USBX, which I cannot, for the life of me, get to work.
Firstly, it straight up wouldn't find the files. Kept giving me errors. I tried turning off USB and turning it back on, and for whatever mysterious reason it found them now. Well, one down.
Now, you have to turn on and download the USB_DEVICE middleware, but guess what? IT DOESN'T EXIST :(
bruuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuhhhhhhhhhhhhhhh
So now I'm stuck with USBX, something that I initialized, but have absolutely no clue how to use, after several hours spent on it.
Also it doesn't really leave me in the best mood after having spent my entire evening on a piece of code that lowered its ability and added the risk of maybe having destroyed something.
As a side note, I did upload all my stuff to github so there's at least that.

I'm not losing my code again. (Even if it still is quite simple)
Madhav ๐
added to the journal ago
Debugging Random Shutdowns.
So the thing is, there is an off chance that my board might be like glitching, because Adafruit IO stopped receiving messages at 3 AM, for whatever reason. When I woke this morning, for some reason the ESP32 was still on, so I just rebooted the board. Since then it's worked just fine until now (10pm) and yeah.
In addition, there is also the problem that it once in a while it will turn on but not boot, and that's why I have some code on the STM32 to reboot the ESP32 if no code is running (it doesn't drive the comms pin high) within a second or two (like I mentioned before)

code for it it isn't booting properly
if (esp_on && esp_on_ticks + 60000 < HAL_GetTick() && update) {
write_io(ESP_PWR, OFF); // Shut it off
write_io(LED, ON); // Turn on LED
write_io(ESP_UPDATE, ON);
HAL_Delay(1000);
write_io(ESP_PWR, ON);
HAL_Delay(wake_pause);
esp_on_ticks = HAL_GetTick();
}
I inturn added this tidbit so that if it's been over a minute with no return on the send messages function, then it will also reboot the ESP32, as per normal stuff, and if it returns too quickly or something it will reboot again.
Anyways, there is also a bit of code on the ESP32 side for that too, but that's just a simple log file, which is putting in numbers for some reason. I'll figure that out later.
with open("log.txt", "a") as file:
file.write(f"{machine.reset_cause()}\n")
Anyways, even after some serious code-look-over-ing, I still have no clue why it won't work. Ughhhh.
Maybe if the same issue happens again at 3AM or smth then I'll know, but the thing is that it will just eat it up and forget about it.
Hmmmm.
Also, my clock isn't accurate at all, and so it's actually like once every 9:45 or something, and thus like it just feels off. I'll have to find a fix for that too.
Anyways, for that last journal, you may have seen my thing about the free song download, and what happened is that I got a copyright notice, and so I panicked, but it was quite easy to fix.
Well, another thing, just as I'm journalling, this is the second time its woke up to transmit data.
Madhav ๐
added to the journal ago
Filmed, Edited and Finished Demo
DEMO!!!!
Yep, I think I got it done. Took me a bunch of time for me to assemble all ~30 screws, but hey, take a look ^^^
I even took the care to mute us out and replace it with some lovely music that isn't synced and definitely didn't take me just 10 minutes to put on there.
I sort of did have to like install DaVinci resolve, figure it out, and then like actually export it, so...

In addition, I also used up one of my three free monthly downloads for music (from upbeat) so there was also that.
(btw that was like my third shot, and I had to disassemble it all over again to be able to update the code.)
Madhav ๐
added to the journal ago
Further cleaned code; Added anti-theft tracking.
Here's pretty much the same code as last time, but as you can see its cleaner
while (1)
{
// TURN OFF ESP32 - if ESP32 sends turn_off signal, turn it off.
if(read_io(RX)) {
HAL_Delay(10); // Pause to see if it was accidental
if (read_io(RX)) { // Re-check to see if it is still on
if (HAL_GetTick() < esp_on_ticks + 100) {
write_io(ESP_PWR, OFF);
HAL_Delay(10);
write_io(ESP_PWR, ON);
HAL_Delay(wake_pause);
esp_on_ticks = HAL_GetTick();
} else {
write_io(ESP_PWR, OFF); // Shut it off
write_io(LED, OFF); // Turn off LED
write_io(TX, OFF); // Stop telling the ESP32 to shut off
write_io(ESP_UPDATE, OFF);
esp_on = false; // Register the ESP32 is now off
HAL_Delay(10);
}
}
}
// TURN ON/OFF ESP32
if(!read_io(ON_OFF)) {
if (!esp_on) {
write_io(ESP_UPDATE, OFF);
write_io(ESP_PWR, ON); // Turn on ESP32 Regulator
write_io(TX, OFF); // Stop telling the ESP32 to shut off
HAL_Delay(wake_pause); // Wait for ESP32 to initialize, avoid turning it back off
esp_on = true;
esp_on_ticks = HAL_GetTick();
} else {
// If ESP32 is on, send it the signal to turn off.
write_io(TX, ON); // Send off signal to ESP32
write_io(LED, ON); // Turn on LED
}
To do this, I (with like no knowledge of C) made two custom functions, one to read a pin and the other to write to a pin.

They do this by taking in an array to tell it which pin and then just writing to that pin. However, thanks to the #define command, I can just pass in a string (like RX or TX) and it will just parse it. Take a look at my above function for more clear stuff.
Ofcourse, I, mr python user, thought that C would have lists, but apparently they don't. Instead they use arrays, and as far as I can tell they can only have one type of thing in an array, so it basically can't work.
I need a GPIO_TypeDef object for the first one (the 'port') and a uint16_t (which i guess is just an integer) for the second one.
Thus, I had to make my own struct thingy or something. (This is one of the only parts made by AI)
typedef struct {
GPIO_TypeDef *port;
uint16_t pin;
} io;
// STATES
#define OFF 0
#define ON 1
// Pins
#define TX (io){GPIOA, TX_Pin}
#define RX (io){GPIOA, RX_Pin}
#define LED (io){GPIOE, GPIO_PIN_2}
#define ESP_PWR (io){GPIOC, GPIO_PIN_5}
#define ON_OFF (io){GPIOB, IN2_Pin}
#define ESP_UPDATE (io){GPIOA, GPIO_PIN_1}
#define wake_pause 1000
but hey, it works! so no complaint from me.
Anyways, after that I played around a bit with the clock, so basically now I know how to change the clock speed, and I configured it to use the external clock I put onto the board (its a 12MHz one, so CUBEMX had a touch of a problem)
Then, to save power, I reduced the clock way down to just 1MHz, which should be drawing just 20ยตA, but for some reason it isn't.
At 160MHz, it was drawing ~16 mA, so about 100ยตA per MHz, (5x more than datasheet), but this was before I added code to run with the timers, and maybe the external clock is also drawing power.
Speaking of which, you may have noticed that there was some code about restarting if I don't get a signal.
Sometimes, what happens is that the ESP32 fails to boot up, so if after about 1000 ms the STM32 is still getting a 'shut down' command, I'll know that it isn't working properly and I'll turn off and on the power on the ESP32 to reboot it. (This has happened quite a few times for some reason)
In doing so, I decided to tune the amount of time it waits, for whatever stupid reason, and just decided to consult GPT. It said that it takes about 50 ยตS for the code to run, so I started out with a wait of ~1 ms. The light just flickered, with power turning on and off rapidly, so I tried 50ms. After several tries I just decided to go to 2000ms (which worked) and now I have settled on 1000ms.
Just a reminder that changing code on the STM32 requires putting in to flash mode, connecting the programmer, building, and flashing, alongside actually testing it, which easily adds up.
After this, I worked on the anti-theft stuff, which took most of my time.
Anti-Theft stuff
So, to track if it has been stolen, my plan is pretty simple. Every 10 or so minutes, wake it up, send the exact location to my MQTT broker, and tada. I can track exactly where the tracker is.
To do this, I had to like actually learn about timers (the above actually came afterwards, but hey this is more organizes)
Apparently, the STM32 has a bunch of timers that you can use, and I tried configuring one for my use, but it just wouldn't really work.
I googled my problem (again) and finally I was able to find out that the stm32 also has a function that will allow you to know how many seconds it has been since the code started executing, and you can just easily use that as a ms tracker.
Well, that is exactly what I did. At first, it was set to just be that the LED would (in a non-blocking way) turn on and off every 5 seconds, and after that success, I started building.
My STM32 code just ended up being quite simple, however my ESP32 code was far more complicated.
if (HAL_GetTick() % 600000 < 2000 && !esp_on) {
write_io(ESP_PWR, ON); // Turn on ESP32 Regulator
write_io(TX, OFF); // Stop telling the ESP32 to shut off
write_io(ESP_UPDATE, ON);
HAL_Delay(wake_pause); // Wait for ESP32 to initialize, avoid turning it back off
esp_on = true;
esp_on_ticks = HAL_GetTick();
}
What that code just does is it turns it on, turns off the command io to shut down, turns on the 'express boot' io and then just waits for it to initialize.
In essence, my ESP32 code is just as simple as the following:
else:
try:
i = 0
while i < 1000:
while gnss.any():
data = gnss.read()
for byte in data:
stat = my_gps.update(chr(byte))
sleep(0.01)
i += 1
if my_gps.satellites_in_use > 4:
break
sleep(0.5)
send_message(f"{my_gps.latitude[0]}", feed='latitude')
send_message(f"{my_gps.longitude[0]}", feed='longitude')
send_message(f"{my_gps.satellites_in_use}s {my_gps.speed[2]}km/h {my_gps.hdop} hdop {int(send_at('AT+CBC', show=False).split(',')[1])}%", feed='other_info')
sleep(0.5)
except Exception:
sleep(2)
power_off(False)
power_off(False)
However, to do this it required basically a complete restructuring of the code and also like actually remembering how my code works.
There are also some other parts of my code, but this is the biggest part.
The biggest part was making sure that the GPS actually had a good set of data, and, at the very minimum, wasn't giving me 0s before sending the data.
While I was testing, I noticed that the ESP32 was reconnecting to Adafruit IO for some reason during every send, even if it was connected, at to figure out why I sort of fell into a rabbit hole.
It turns out, the problem was that my code for sending the info was lagging, and that was messing up the code that checks if the modem is connected.
I wish it were an easy fix to do that, but I had to like write my function to now check when the modem has returned the correct thing to then send the data after that, and then write the code, wait for the modem to get back, and then only continue with execution.
Well, for some reason that journal took even longer than the rest.
It was ~40 minutes.
I'm probably missing something, but I really can't spend anymore time journaling โ it's getting far too late.
Madhav ๐
added to the journal ago
Made Code Simpler; Learned Booleans; Commented Code
Yep. Did you know that C, natively, doesn't have booleans?
Well, quick fix, I just included <stdbool.h> and then it worked. Still really feels weird that it isn't the same color as the int or voids.
I needed this boolean, because I wanted to keep track of weather or not it has actually turned on or not, so that I can just use the same IO to both turn it on and off, as it now is. However, it really is a pain to understand what is happening (even if it is my own code) and thus I decided to comment it. Like pretty much every line has a comment. Take a look:

while (1)
{
// TURN OFF ESP32 - if ESP32 sends turn_off signal, turn it off.
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10)) {
HAL_Delay(50); // Pause to see if it was accidental
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10)) { // Re-check to see if it is still on
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_RESET); // Shut it off
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_RESET); // Turn off LED
HAL_GPIO_WritePin(ESP_TX_GPIO_Port, ESP_TX_Pin, GPIO_PIN_RESET); // Stop telling the ESP32 to shut off
esp_on = false; // Register the ESP32 is now off
HAL_Delay(100);
}
}
// TURN ON/OFF ESP32
if(!HAL_GPIO_ReadPin(GPIOB, OFF_Pin)) {
if (!esp_on) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET); // Turn on ESP32 Regulator
HAL_GPIO_WritePin(ESP_TX_GPIO_Port, ESP_TX_Pin, GPIO_PIN_RESET); // Stop telling the ESP32 to shut off
HAL_Delay(1000); // Wait for ESP32 to initialize, avoid turning it back off
esp_on = true;
} else {
// If ESP32 is on, send it the signal to turn off.
HAL_GPIO_WritePin(ESP_TX_GPIO_Port, ESP_TX_Pin, GPIO_PIN_SET); // Send off signal to ESP32
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET); // Turn on LED
}
}
Feels so profesional, doesn't it ^^
Next up, I'm going to rename all of those pins and stuff so that they actually make sense, and render my comments mostly useless.
Madhav ๐
added to the journal ago
Failed to get ina226 to work even after spending ~7 hours
Ok so basically, the tldr is that I tried to get libraries to work with the STM32 but couldn't understand it, so I tried getting the Arduino IDE to work again, it worked, but some very important pins like PC5 were not supported and thus I can use it either. Next steps are to either try getting my real board in the STM32 interface or building a simple custom library in STM32 HAL.

No, I did not mean PC_5
Now the long story.
So, as I said, I wanted to get the other sensors/E-ink working.
That is basically all that remains to get the entire thing good, and functional.
First up, I tried the E-INK, as there supposedly is support for it to work. However, as it is typical waveshare, so I can't understand anything.
Well, I did understand a bit. So it seems like its set up in having .h and .c files all together in a folder, but they keep pointing to each other so its quite hard to understand. Not to mention like all the libraries are there, including those for like every single size they have ever made.
Well, that didn't work well, so now I decided to go and try the INA226 libraries. And, guess what. Not a single library I could find was recent, except this one that is some 10 or 11 years old.
I guess I'll have to try that later, but if even the INA226 doesn't have one them I'm gonna be pretty surprised if the other sensors have them.
So, I sort of thought, what if I tried to get the thing working with the Arduino IDE.
And, believe it or not, it actually worked.
If you look at my journal a bunch of time ago, you will see that I said that I failed to do it. I still have the problem that the Arduino IDE doesn't officially support my board, but I was able to find a close board to that that allows me to toggle the first pin I tried, the LED pin.
So, with the Arduino IDE, I was able to actually program the board to flash an LED.
However, when I tried moving the program over, it didn't work the best. It gave me an error with pins PB12 (not too important) and PC5, the most important pin. PC5 is the pin that toggles the ESP32's power.
Whereas my current board goes through the following phases:
I litterally wouldn't be able to turn the ESP32 on/off.
I had spent about an hour thirty at this point, and so I went off into google to find out if I can get that pin to work

btw, I had already had some trouble with the pins not mapping properly, so I had set up some programs to guess and check the pins to see which is which.
for (int i=10; i<50; i++) {
//Serial.println(i);
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
for (int i=30; i<37; i++) {
Serial.println(i);
pinMode(i, OUTPUT);
digitalWrite(i, HIGH);
delay(2000);
digitalWrite(i, LOW);
delay(1000);
}
^^ this was my code to guess and check the PC5 pin. I went through all GPIOs that were close, and the ESP32 didn't get power :(
So, after that, I went around looking for the stuff. I found out that the problem was related to the Q port on the test board that I was using, and the options without the Q ending didn't have PC5 to begin with.
Basically, the Q port uses the pin mapped to PC5 normally as I think an SMPS pin, so it literally won't work. It only now makes sense why it wasn't working with that wide array of test pins.
If you look, the most frustrating part, is that the Arduino Core github repo actually has the STM32VGT folder, with the proper mappings for my board, but I can't select that from the Arduino IDE, and thus I can't use the Arduino IDE.
ofc, this took a lot longer than you would think, as like you actually have to find out what is up, including looking understanding how c++ works as a total beginner (I have literally never used the Arduino IDE nor the C++ or C, except like one program for SOM)
So yeah this is where I've spent my last like 6~7 hours. It might not seem like that, but it really does take a long time.
And now what should I do next.
As I briefly touched upon in the TLDR, I really only have two options. Either figure out if I can get the correct board settings with the Arduino IDE, or build the custom libraries in the STM32 IDE. Both options are a real pain, but here are the pros for each:
| Arduino IDE | STM32CUBEMX |
|---|---|
| Simple to use if I can get it to work should not require me to re-write libraries for each of the (only) 3 sensors and the E-INK | Gives far more control and easier to optimise; can change the clock speed and other such stuff far easier |
| Simple to use, well supported | Certain to work, but a lot more work |
| Easier to program, included debugger so that I can actually have a working serial monitor to see what is going on |
So, basically, the thing is do I want to have the chance of wasting my time in trying to get the Arduino IDE, while sacrificing performance, or do I just want to go for the STM32CUBEIDE, which will work for sure, but is definitely going to be a lot more work. Well I guess I have a lot to think of tonight.
As always, to make sure my journals are as detailed and engaging as possible I have invested a lot of time into it, with this journal taking me ~35 minutes. I am logging this time.
Madhav ๐
added to the journal ago
PROGRAMMED STM32
So if you read my last journal, you'll know that I was having a bunch of trouble with the battery bouncing around and messing with the cell chip, and thus in the meantime I've worked on some code in the STM32CUBEIDE to allow me to basically short out a set of pins to shut down the ESP32, and another set to wake them up.
Basically, the wake up is quite simple, but the shutdown just signals the ESP32 to stop doing whatever it is, and then the ESP32 will shut down the cell chip. In turn, it will tell the STM32 that it's done, which will then cut off power to the ESP32.
I had a few problems where the STM32 would keep detecting a false low and turn on the ESP32, and even when it returns and tells it to turn off, it would flash off for like a millionth of a second, and then turn back on, a time period which was enough for just the capacitors to smooth out.
Anyways, I still have no idea why that happened, and my current work around is to just use a different pin. It could be that for some stupid reason I wired it up to two different IO pins on the STM32, and now I'm stuck with that pin not working. Thankfully, I designed so extra screw terminal blocks, so I will be (most likely) ok.
I also had a bit of a problem with me thinking that the terminal block was flipped around, so I shorted two IOs together, not an IO to ground. Simple fix, and all went well. (After like 10 minutes of debugging why its not working)
Anyways, I also had some timing off so that it would wake up, and the ESP32 wouldn't have a chance to tell the STM32 not to shut it back down, and the STM32 would do exactly that. Again, simple fix (long time to find) and I just added a bit of a delay.
I still don't have the touch/ina226/imu/mag wired up, but it all will be slow, and I will most likely just submit it up before that as I will have to find libraries and stuff.

I was thinking, I might just make it so that the same button will turn the ESP32 on or off. Hmmmmmmm. I think that might be a bit hard, but oh well.
Madhav ๐
added to the journal ago
Assembled/Tested [slight retro]
Today was probably the nicest day this year, so far, and thus I took that opportunity to take it out for a bike ride. However, I had to assemble it first. There was mostly just the thing that I assembled it, but then I remembered that I had to flip the screen (in code) so I had to disassemble it to reprogram it. After re-assembling it, for some reason the GPS hung out, and thus I started to disassemble it. Lucky me, for some reason after just removing the first screw, it looked like it restarted, and started working.
However, this was the first sign of foreshadowing
I simply thought "Ooh, how lucky am I!" and continued on.
Once it was on there, I took it out for a bit, just down the street, and ... the wind! It was soooooooo windy. The advantage was, however, that on the way back I easily clocked (what my speedo says) around 32~34 km/h
The bike mount part looked a touch angled to me, however I just ignored it, thinking all would be well.
At some point, I was also thinking of what the biggest reason for it to fail would be. As I only had 3 screws on the bike mount (out of its designed 6) due to the mount flexing too much to fit the rest in, I thought that would be the biggest point. All other pieces bore relatively less weight, and were pretty sturdy, with several screws. The battery clip, for example, was mounted with 8 screws. (Believe me, it's a pain to put in on and take it off)
Anyways, I thought something along the lines of if a screw strips, I'll just take a look at it later. Definitely not foreshadowing.
As all was going well, I went for a longer bike ride, a 15 km loop. And it was here where disaster struck.
I was about 500 meters in, where I noticed that there was quite a bit of a flex/offset on the bike-mount part, but I thought that it was probably just rotating, as perhaps the friction was a bit low.
I also suspected that the wind, which was easily gusting to ~50km/h was also perhaps to cause. Now that I think of it, maybe I should have paused to see if all was well, but uhhhh I was sort of stupid.
So, I started noticing that, at the 3km mark, the screen was wobbling enough that the battery clip (which's spring conducted so much power that it shorted together [this was when I accidentally shorted the antenna to the power pin]) was starting to give way at some times, resulting in the cell chip losing power without it shutting down. The datasheet clearly says not to do that as it might corrupt its software, and thus I thought of turning around. However, as I had already scaled the uphill and the upwind part, I didn't feel like undoing all my progress. I thought that I'll just go slower, feel less bumps, and continue on.
I continued on to the 5~6 km mark, and disaster struck.
It was on a downhill, with the wind helping me, and even though I was trying to go slow I was probably clocking 20 to 25 km/h. And it was here that all three screws stripped. And the entire assembly, screens-first, fell onto the asphalt.
Thank goodness, however, it fell at a tiny bit of an angle so that the plastic absorbed some force, and the rest was on the screen with the screen protector on. Thus, after some cleaning, the screens look perfectly fine, but the main truss (that I was going to reprint for some issues anyways) has dirt on it and is a bit damaged.
Now, the painful part. I had to walk my bike for the 6km home, as the wind was gusting too hard for me to bike with a single hand. (I had to hold the odo in one, ofcourse)
Occasionally, I would take the risk and get on my bike, but everytime the wind became to powerful I would get back off.
Thankfully, after getting home, I was able to test it and confirm that all functions work.
JOURNALLED TIME
As this included coming back home, I will NOT be logging that time, but the going there (testing) time and assembly is be logged. Journaling took 30 minutes. (I am not kidding I seriously write really detailed stuff.

Google maps shot of where it failed. I did not have my phone with me for the entirety of this ride.
Madhav ๐
added to the journal ago
Got a 3D print, assembled, updated code
ok well that was ... a bus ride
so basically, i had to go get a 3d print for my #blueprint project, so I basically just went somewhere, and the thing is, I thought I started the print, but it didn't actually start, so when I came back to check on it an hour later, I saw it wasn't there. (It's a 30 minute print)
So, I asked the staff if they took out the part, but no, they couldn't find it. So, I'm like panicking, where did it go?????
We go to restart the print, and this time I sit there for a minute, and I see that the print doesn't start. We try different SD cards, and all that, still no progress. Then, we literally just restart the printer, and it all just works. 35 minutes later, I am running to the bus stop, in pouring rain. Like actually pouring rain
Like soooooo pourring that the water is bouncing like 5cm off of the grass
well that was how long it took to get the print and stuff, but well yeah that's life ig. (total unlogged (but journalled) time for print is now ~6 hrs)
Anyways, I also worked on the assembly, and I'm glad to say it works!








The screens are just flipped, but that's a simple 0 --> 2 change.
I also worked on getting the gps to work a bit faster, so now it's updating at 5hz, vs 1hz before. This way, it will feel more responsive. I was wondering why the command wasn't echoing, and apparently it must end in /r/n, and also apparently you need to tell it which pin is the tx pin. Huh, wouldn't have guessed.
Anyways, once I figured that out and got it to work, the satellite info was becoming fuzzy and changing too much so I made it only display the first 5 satellites (in order of signal strength) and I think all is well!
That assembly took a while as a component was a bit too big and thus flexed outwards so that the screw holes wouldn't align.
Madhav ๐
added to the journal ago
Updated Bike Mount Part to Fit.
Yep, that's exactly what I did. I extended the part, resized the holes to become perfect (hopefully) and I also made is so that it isn't the thick diameter all the way, just for a small area, which of course is filleted.

Madhav ๐
added to the journal ago
PICS!!!! & Assembly
I assembled like 10 screws to just have like the bare minimum structure, but I had to disassemble it before going somewhere (to turn it off) and well here it is!













And here is the bottom side with the clip-on and screw-on-able cover off. Notice that one battery clip is not soldered on because I forgot to include it in the BOM and like yeah.








Fully Assembled (minus the battery-clip) and the wires stuffed in.





I really need to make some cable-management slots in the new truss, but it pretty much works as is rn.
The battery-clip and stuff are a bit tight and hard to put together as I have to undo it all to turn it off, thus I don't have the pictures rn. It needs some software to fix that so that just hitting a button (which needs cutouts) will shut it down and power off the ESP32.
Madhav ๐
added to the journal ago
Over Discharged my LIIONS(to 2V) & software & testing
I wrote some software and stuff to show the satellite information to see how many satellites are visible and connected. In addition, it also tells you, for each satellite, the signal-to-noise ratio, snr, and somehow I am getting ~49 outside, with high 40s for like 5 satellites, with a patch antenna. As if this isn't crazy enough, my HDOP is like ~1, and I am getting like ~35 near a window (inside) and like 30 near my monitors.
And yeah I left it to run a long test, and the battery really got over-discharged, as the buck-boost converter was still supplying my esp32 and screens with stable 3.3V. My Cell chip, however, was unhappy and shutdown. Since I was away, however, it went on for like either 2h10 ish or just 10 mins after the cell chip cut out at probably 2.5V.

just some data: time, displaying percentage, %, actually percent, time stamp utc, pin value of the shutdown pin, newline, time it took to update LCD, check uarts, issue command for battery percentage, etc.
I also did some tests outside, running around, going for a walk and all that, and I am happy to report it looks very accurate. I was getting, on the high side, about 22 km/h and like 5 km/h for my normal walk, which lines up with my estimates.
Madhav ๐
added to the journal ago
Software and assembly
I got the rest of the components!
After removing supports, here is the structure!







In addition, I worked on the software, which is why
(i'll add pictures next time if i remember)
I have new software running that accurately uses gnss time and speed for display. It took me a while because half-way through, my computer glitched out, asked for 60 gigs of ram, and crashed in my face, taking with it some of my progress.
I have not only the speed (this time from gps, not just a placeholder), but I also have the time displaying and the battery percentage. The battery took me a bit because I had to find a way to decode the easy-to-read but not parse text from the cell chip. (The cell chip can measure battery voltage and percentage)
The time was its own headache because it just wouldn't sit right and like I had to find a way to change the timezone from utc. I installed a library that I couldn't figure out how to use, so my current setup is just the utc time, minus the offset, modded by 12. So, if its 0 o'clock utc, i'll get a negative number that will then mod back to a positive one, and all is well. It just can't handle dates at all, but hey, I only need to know the time, if that, while on a bike ride.
I will be prepping to submit this, even though the STM32 portion isn't really done, as the deadline is coming up way too fast.
Madhav ๐
added to the journal ago
Threaded Holes
Well that was a lot of holes. I ran the screws in the holes to thread them. For the holes that were not so usable, I first drilled out the support material, and then threaded them.
I also mounted the screens onto the main trus, but I have a problem that the holes are all too big on the main truss, and thus its basically impossible to keep the e-ink on, but I don't quite have enough time to fix it, so ig it'll just have to work for now. I'm going to get the last parts today, (if they didn't fail) and I'll thread them and everything for tomorrow so I can go test it!
I'll just quickly ( ** hopes ** ) finish them off.
I keep forgetting to get receipts, so the majority of the prints are coming out of my money. Already burnt through $10.


Madhav ๐
added to the journal ago
3D Model Fix & Coding
So I got my parts, but as they required support material, they are all cooked. Well that was a nice quick fix, but I lost an hour or so in going to start the print and the getting them.
But I just realized, right as I'm writing, that I forgot to add a hole for the FFCs, and so I'm gonna have to route them in the most awkward weird way. I'll fix them in a later revision.
Since I have (most of) the parts, I can now see that this thing is gonna be quite fat, and fingers crossed it still looks alr.




And after that I spent 3 and a half hours coding to make the text as big as you can see above.
This including learning how to make custom fonts.
The custom fonts were the hardest part.
The custom fonts were the most time consuming part.
The custom fonts were (pretty much) the only part.
Uhh no actual there was also one small other thing.
Anyways, here's my list of errors, after spending like an hour trying to force my brain to understand how the font generation works. By the way, it doesn't help that the docs and everything are pretty much non-existent and really old.
#1 Wrong function: I used monofont2bitmap, which isn't compatible, whereas font2bitmap is. ????????
I still had to debug it to give me an output, which included installing a library, finding out it was too new and didn't have some functions, vibecoding a fix, and just wondering why????
In addition, its debugging was ... not so good?
Spent far too long trying to see why it kept giving a (seemingly) random error, and as it turns out, I needed the -c or -s flags to tell it which chars... WHY CAN'T IT AUTO DO THE ONES I NEEEDDDDD????????? :(
And after all that, I was wondering why the write function was failing. Firstly, it was because of using the wrong function (more later) and because this function gives an incompatible bitmap. How nice.
#2 Right function, wrong writing method
So, I go in and use the text function, as I've always done, and it throws a random error. No clue why. ChatGPT gives me the right answer, but I thought it was hallucinating. So, digging through the very outdated docs, I actually find it! The correct function was the write one.
and that pretty much would have fixed it, if not for me being in the wrong file
#3 Wrong File:
So like it should be really hard to edit the wrong file, right?
Well no. When you have two files, one for the old code and one for the new, that can happen. I used the write function with the in-built font (which requires text) and it didn't work.
How nice
Minus an hour (like 10 mins) of my life I will never get back.
So, once I find that out, I immideately rage-close the file, and well everything went nice afterwards.
I just spent a bunch of time getting the font size right, getting it centered and all that stuff, and getting 1 to become 01, etc.
Well I probably forgot smth, but that should be the gist of it!
Madhav ๐
added to the journal ago
Worked on connection script optimisation
I made a new connected function which basically uses AT commands to detect if its connected so it doesn't reconnect, unless the connect functions' force=True (default False)

I also implemented it, and also did a 70 minute test with a message outgoing every 2.3 seconds to Adafruit IO, and it ended up using about 200kb (for ~1700 messages)
Looks pretty good to me!
I also stated implementing the show flag, to hide outputs for useless commands, such as AT+SMSTATE? and AT+SHSTATE? as the connect function will handle and tell me what happens.
Madhav ๐
added to the journal ago
Got Two Screens to Work
Spent a bit of time modifying the code to drive both screens simultaneously, and here's the result!
Only one minor hiccup, I accidentally defined two different SPI lines for each, using the same pins, and micropython didn't like that.



.jpg)





WIN20260322195041_Pro
Madhav ๐
added to the journal ago
Tried To Get Arduino IDE
I know that all my previous journals always had stuff getting working, but this time it was very different.
Arduino IDE logged 40 minutes of me trying to get it to work, but if you include googling, cubeMX programming and all of that (not-so-fun) stuff, it was a bunch more.
Anyways, what I basically did was just spent my entire time seeing why my board wasn't showing up, while there were others, and I'm not even really understanding why, but my chip just isn't supported, so I'm going to have to use HAL and STM32CUBEMXIDE, which is even harder :(
lowk i just wish we could get micropython.

Madhav ๐
added to the journal ago
Got Screens To Work
So yeah, basically I got them to work!




So, I had already got the screen to work, but that was by connecting them to my rpi Pico, which apparently has more RAM than my ESP32 (not PSRAM, RAM)
So I got this lovely error:
MemoryError: memory allocation failed, allocating 153600 bytes
New Library
Ok, well the official waveshare one didn't work, so I thought of just googling the chip, the ST7789T3. And right there, I found the ST7789 Library, something that I have even used before!
The thing is, I'd forgotten a bit how to use it, but mostly my brain was cooking, and I flashed the new micropython file and
...
...
LOST ALL MY FILES :(((
It wasn't that bad, as I had my main file backed up to my computer just in case, so thankfully, it's mostly all well. Just for my own stuff, I'll put it at the end of this journal too.
So, with this new (outdated) micropython file, I tested my original code, and there was just a tiny difference, with the way you assign pins for uart, but other than that all was well.
So that still wouldn't explain why it wasn't working.
Well, turns out, I wired my display connector wrong. What I put as pin 1 was actually 18, and vice versa all the way.
Thankfully, the geniuses at waveshare thought that someone might be that stupid, and thus they made their conector have contacts on both sides, so you can insert the cable either way, and swap it around so that all is well!
And after that, all went quite well.
Anyways, I'm a bit concerned about the amount of power the cell chip is drawing, as its quite a bit. In all of this testing, the voltage has dropped by ~0.3V (so maybe ~.1V per hour? I guess that's not terrible) from 4.008V to 3.758V
I really wonder why I'm so slow at journalling: this one took me 15~20 minutes
Madhav ๐
added to the journal ago
Got 2x 3D printed parts
I went to the library and just sat there for ~30 mins waiting for my print. Gonna have a lot more of that...
Anyways, I just barely missed my bus home, so I had to wait an extra 30 mins :(

I'm not adding the 2hrs.
Madhav ๐
added to the journal ago
Finished 3D Model
Yep!
I fixed up tolerances, and added the final screw holes!






That means 6 holes here (3 both sides)

which connect to the battery clip here:


Which connects to the body here:



This side didn't have enough space, so the chamfer and screw hole actually goes a bit onto the sides:


I also fixed up the display holes so that they would actually hold the screw, and not just let it slide right through.

Madhav ๐
added to the journal ago
Cleaning Up Basic Code & HTTP SADNESS
So I spent an hour or two cleaning up my code, so basically it's like clean and working well.
Whereas my previous code was comprised of functions and AT commands every where, my current setup is having a proper function, with some comments, but not very many.
def connect_to_cell(server, url=''):
global connected
if not powered_on():
power_on()
send_at('AT+SMDISC') #DISCONNECT FROM ADAFRUIT IO
send_at('AT+SHDISC') #DISCONNECT FROM HTTP
#send_at('AT+CGDCONT=1,"IP","simbase"')
while "99,99" in send_at("AT+CSQ", wait=.1): # signal
sleep(0.3)=
while "NO SERVICE" in send_at("AT+CPSI?", wait=.1):
send_at("AT+CSQ", wait=.1)
sleep(0.3)
send_at("AT+CGACT?", wait=.1) #active?
send_at('AT+CGDCONT?', wait=.1) #ip & stuff
send_at('AT+CGPADDR=1', wait=.1) #connect to wifi
send_at('AT+CNACT=0,1', wait=.1)
send_at('AT+CACID=0', wait=.1)
if server in ['io', 'adafruit', 'mqtt']:
send_at('AT+SMCONF="URL","io.adafruit.com",1883', wait=.1)
send_at('AT+SMCONF="CLIENTID","esp32-sim7080g"', wait=.1)
send_at('AT+SMCONF="USERNAME","space_coder"', wait=.1)
send_at('AT+SMCONF="PASSWORD","aio_ttzT34pSKp3WBIRbIASSLQfqNBqA"', wait=.1)
send_at('AT+SMCONN', 1)
if server in 'https':
send_at('AT+SHCONF="BODYLEN",1024', wait=.1)
send_at('AT+SHCONF="HEADERLEN",350', wait=.1)
send_at(f'AT+SHCONF="URL","{url}"', wait=.1)
send_at('AT+SHCONN', 5)
connected = True
(cleaned up version)
That, above, is my connect function, and as you can see in addition to cleaning up the code, I also figured out how to speed it up so that it works well. Whereas my previous function would take well over 15 seconds, some two seconds per command, now it takes about 3 seconds to connect to Adafruit IO, or about 7 seconds to connect to connect to a webpage (I'll get to this later).
I also set it up with the DTS and RTS commands, so that if I want higher bauds, in the MHz range, I can.
Furthermore, I re-setup the UART, as previously there was a built in slowing function, adding about 1 second to every read.
HTTPS
So this part was just like the others
I wish.
The other cell features were actually pretty easy, but this time chatGPT started overly hallucinating, so that is why it took so long.
Firstly, there was the pinging. To get it to send a ping, I had to find a good source, as GPT wasn't working. This one had a good chunk of the stuff I needed, but it was a bit confusing, as the APN was different on their page. All in all, the pings were quite easy, and then there was the actual reading part.
Reading is as simple as sending send_at('AT+SHREQ="/",1', 10) and then send_at('AT+SHREAD=0,528', 10)
But the connection part is a whole other mess.
So as you can see, my current connection function asks whether I want to connect to IO or HTTP, and that's not because of speed or anything. That's because 90% of the time it cannot connect to both. So the correct course of action would have been to disconnect from the IO, as I now do, and then connect to the HTTP, however I didn't know that, so I was just like stuck and wondering why my connection code wasn't working, but the example did.
Once I figured that out, it was quite easy, really. Just make sure to disconnect, and all should be well.
The Pain
So, I decided that I was going to clump the functions and clean them up so that it won't waste time and data connecting to the MQTT if I wanted the HTTP, and vice versa. So, I put the code in and it just didn't work. I debugged it for a solid hour and I finally found the problem. I had commented out the lines of code that told my modem how much of a buffer to assign to the read function, and voilร , nono functioning.

Uncommenting those lines pretty much fixed the entire problem, and yeah.
3D printing
I went to the library today to see if they had a printer open, and, even in this weather (its raining/cold), there was not a printer (out of 2) that was open.
Also, I'd just like to note: picture are going to be hard as I don't have much to show, other than code, and I don't have a camera.
p.s. This journal alone took me 17 minutes of locking in/focusing.
Madhav ๐
added to the journal ago
Got CELL TO WORK & soldered esp32
Most importantly, I got the CELL STUFF TO WORK!!!

But anyways, the story
Soldering
Firstly, there was the soldering.
Preparations alone took me like 30~45 minutes, as I had to find a space, clear it, and then the difficult part: find the components.
So, the thing is, the ESP32s sort of disappeared, but were actually where I though they were all along, so minus 10 minutes (yes, I know, I'm slow)
Then, the same thing happened with the Flux. Like it shouldn't be so hard to find a big tube of orange goo, when I don't own anything orange.
(idk where it was now, and i lowk managed to lose it again ... nvm found it)
Anyways, then the flux tube wouldn't open.
At first, I thought that I stored it wrong or something, but no it was alright. So then why would it not just open?
because it was too cold
All I had to do was just add some heat, and voila it just opened
THEN, on the first board I guess I messed up and put too little flux or something, but when I soldered the ESP32, it just didn't go very well, and uhh i also tried pre-adding solder to the pads and that ended up just desoldering the decoupling capacitors.

Thankfully, the second set of stuff went much easier, and I just powered it and connected it and it just worked!!!!
The default firmware on these chips is cooked, so that's why I had to quickly flash new firmware before windows gave me a headache of complaints.
Alls well, though, and it just worked.
Next up, the switch.
Stupid Switches
Don't you just love alliteration.
Anyways, normally, when you slide the switch to the right, the right and center short, whilst the left two are not. Right????????
Nope. Not on this switch. That took me sooooo long to figure out why it wasn't getting power (the cell chip)
It took me so long to get the wits to actually bother to check the resistance and its behaviour... :(
Well, whatever, it works it works.
Coding & Cell
Ok, now the sad part.
When I was testing the stuff for voltage, I accidentally shorted the outside of the antenna to the VBAT pin, and who would think, the outside is connected to GROUND.
So well, what happens when you short the pins like that? Smoke. Lots of it. Well I was about to give up at that, but I thought: "hmmmm what if I try and plug it in anyways?"
So I did.
Plugged the battery in, and the STM32 turned on (it was programmed just to blink its LED)
Turned on the switch for the ESP32.
And.
IT ALSO TURNED ON!!!!
So basically, as far as I could tell, all was well.
But there was still the CELL CHIP. I knew for a fact that it was a very sensitive chip, so I was just given up on that and willing to accept a functional ESP32/STM32 devboard.
So, when I first failed to turn on the Cell Chip, I was sort of just confirming my suspicion. But, against all odds (after 30+ minutes) I decided to consult chatGPT.
I don't normally ever use it, as it hallucinates random code more often than useful code.
But it actually worked.

my shock ^^^
Anyways, that was absolutely great news. Despite smoke coming from my board, it just worked.
Then, I just fumbled with chatGPT to give me AT commands, and it, again, just worked.

However, there was this small problem. I was getting signals once in a while. However, thankfully, it was just warming up, and now it connects reliably.
The next problem was actually getting Adafruit IO to receive data, and that was done by just taking a look at what AT commands Waveshare uses on there SIM7080G boards, and copying them. (More like adapting them to my framework, as they were written for the Raspberry Pi)
And yeah, in short, that's all I had to do. I sort of glazed over the hours of pain that it took to get it working with the SIM module, but hey, all's well that ends well.
I also had the horrifying thought of giving up hardware. Super happy I didn't!!
Madhav ๐
added to the journal ago
PCBs ARRIVED: GOT THE LED TO BLINK!!!!!
Yep. Believe it or not, the naysayers and the haters have been shut down for good.
Those little-lings will now not be able to call me "one-who-cannot-program-an-stm32" as that is just what I did.
So it was a real mess trying to get everything to work, but really all I had to do was just ask @tty7 to tell me why it wasn't working, and all he had to do was tell me that you actually have to press the "boot" button for picky Ms. Windows to detect the STM32.
From there it was smooth sailing! more pain...
So now I had to figure out how to program it. @tty7 wasn't gonna be available for an hour, so instead I spent that hour in the googles. Firstly, I looked for a way to program it. Arduino IDE maybe? yeah no that didn't work
So I came back and found this epic video detailing exactly how to program the STM32 by USB
and From there it was smooth sailing! more pain...
yeah this time I had to like actually find out how the main things work, and considering the include the main() functions box was unchecked, that must have been a real nice, easy, time.
So once I rechecked that box, added these two lines of code:

and then flashed it like how the video said, and it just worked!
Lowk so happy
oh yeah forgot to say, my PCBs ARRIVED!!!!!
lowk so cool. I need to solder the ESP32 on, find libraries for my stuff and maybe not even use this, repent for making a micropython library for text scaling, and yeah.
Also, I'm paranoid I'm going to plug the battery in the wrong way and cook my stuff, so there's that. Haven't yet soldered on the battery holder, so once in a while power just cuts out. (the USBs don't provide my board with power)
Madhav ๐
added to the journal ago
Added: S O M A N Y S C R E W H O L E S
yep, that's what I did.
Firstly, there is the top side:

A TONNNE of screw holes. It should be noted that the screw hold on the right side, bottom left of the box, is just there for show.
There is nothing for it to dig into.

Back Side:
Here it is:


This all required me to cut out the bottom plate so that the screws can fit, as there is really only one screw type that is going to be used in all the holes.
The hard part: PCB Mount
When looking here, its hard to see:

But if I hide the PCB:

Not only did the hole alignment take so long, but also the fillets took just s o . l o n g .
s o o o o o o o o o . l o n g .
They kept on giving me errors, and all that nonsense. At least I didn't have to chamfer these holes.
Chamfering
If you looked really closely, you'd see the chamfering:

This is so that the screw can sit flat, and this too took so long, as google just couldn't tell me how to chamfer. (It was really simple once I found it)
Now all I need to do is the back mount!

PCB should be arriving tomorow!
bonus:
(main truss, looks so tuff)




Madhav ๐
added to the journal ago
Worked On 3D Model: Bike Mount
I speedran the bike mount, the place where the speedo will mount to the bike

This is my first try at clip-on 3D prints, as the hole is a millimetre or two too small, so it's gonna be under a nice amount of pressure and thus gonna have a tonne of friction.


It's also designed to be screwed on right here to connect to the main body.
It should be noted that everything is a touch too small to make sure there is sufficient friction.
It is also nice that the bar is vertical,

So that the friction only has to prevent it from rotating sideways, not up and down.
I'm still a bit confused on how to connect it and everything, but its gonna probably be that the mount screws onto the back battery plate, which then has screws to the main body that are gonna have to be taken off everytime the batteries need to be charged.
Now all I need to do is add the screw holes, and I should be done!
Madhav ๐
added to the journal ago
Pretty Much Finished 3D Model
So yeah, I Pretty Much Finished 3D Model.

All I really need to add know is the bike mount, and the screw holes to hold everything together!
So, pretty much, all I worked on was the back part.

This part will hold the PCB, in such a way that, with screw holes, will be able to also accommodated the batteries in a way that they can be removed for charging.

Where the more turquoise blue is is where the batteries are, and that is also where the thing will come off for when I want to recharge the batteries.
This might not seem like a lot, but compared to my last render, this whole workflow just happened much faster, and I'm actually quite proud.
And this is even after me having to redo much of my work, as V18 got deleted, so I had to go back to V17, and there was this problem:

Even though it's just the inside, I wanted that to also look nice, so that's why I wanted to fix that up, but the normal way I would do it in fusion 360 didn't quite work as it would keep say computation error. Thus, I had to go google it, and it wasn't of much help either. So, I just tended up deleting that bad part, and just extending the sides all the way to the cover that area.


Here's what the inside looks like:

Again, where its more turquoise is the battery area, whereas the bluer area is the other mounting part.
Just reworked some stuff for tolerances, so here it is:
PCB CASE:



FULL:



Madhav ๐
added to the journal ago
Re-3D modelling
I finally came around to do it, and so here it is!
Not nearly (or even close to being) done...



So yeah, white is e-ink, screens are obviously screens, pcb is also obviously the PCB, and the beige is the main case. The orange is the mounting pieces that are going to be used to mount the E-INK on, as it doesn't have useful screw holes.
However, it does have a pin header, that has this hole for it:

(The center is also shallower as there is the E-INK driving circuitry on the back)
Not much more to say, other then the fact that I'm worried about the GPS performance, as it has the batteries right under it, even though there is supposed to be a keepout. In addition, the E-INK is also quite close to it, but I think it should most likely be alright.
I'm really proud of the fact that I was able to finish this so quickly, taking just about two hours.
Next time, my plan is to add the main PCB mount, and bother with the screws.
I also will have to figure out the bike mount clips, but I think I'll do that part and the battery mount section later, after I get this part printed out and tested.
Madhav ๐
added to the journal ago
LCD & E-INKing
Alr, so, believe it or not, the LCD just worked first try!!!!!!
It was lowk so easy, took like 10 mins to wire it up (and double check and stuff) then I just found the official code and it just worked!!
While I'm at it, the screws also arrived, and hey, they fit!
And now the really time consuming part. E-INK custom text scaling!

Just so you know, I did, in fact, get it to work, but the thing is I just thought it would be as simple as just copying the LCD text scaler function over, and it would just work.
ye ofc it wasn't
But first, before I get to that part, I just wanted to talk about how I really goofed up in reading the E-INK code, so previously it would just drive the E-INK when the vertical_write() calls would come in, and not for the horizontal_write(), and I was really wondering why, and it turns out that I didn't change the SPI pins on the Horizontal class, just on the Vertical class.
Quick fix, just changed that, (but not so quick to find it, ofc) and now instead of waiting 10+ seconds for the screen to initialize, it just does so within a second!
So yeah, now that I had got that to initialize far faster (a problem I had been observing for quite a while, but was unsure of why), I decided that if my speedo was gonna be readable in bright sunlight, it would make most sense for the E-INK to be able to display speed, too.
Since, I needed it to be visible at a bit of a distance while shaking, I decided that I should find out how to make the text big enough to be readable. Hence, the problem
So I briefly touched upon how the LCD has a scaler, which it does, but the thing is that it's designed for a different setup, with colour displays and stuff.
So, I went looking online and I found this.
Basically, it does the exact same thing, but somehow works (after much modding)
"What modding," you ask?
Well firstly, I had to actually understand the code, and here's a rundown of what it does.
Put simply, it take micropython's framebuffer's built in font, and then reads it, by putting it into a new framebuffer and reading every pixel.
Then, it takes those readings, and draws rectangles that are the scale. So, if we were drawing to a 2x scale, it would draw the rectangles to be 2x2 pixels, instead of the default 1.
However, to do that, you must actually first make the framebuffer, which apparently can just be done by this:
me forgetting and searching my code
ok so yeah its actually really simple, just go
temp_fb = framebuf.FrameBuffer(temp_buf, width, height, framebuf.MONO_VLSB)
(if you have imported the frambuf library)
The problem just was that the code there used something different, and I have basically never used framebuf.
Alright so once that is there, there is still the fact that normally, 0x00 is black, and 0xff is white right?
Well for some reason that was flipped
I was writing 0x00, and I was getting white, while 0xff was giving me black...
(I now know that that was just because of the fact that the temporary framebuf [the one with the micropython default fonts] was the right one, but then it was being flipped by a stupid piece of [my own] code.)
By the way, writing these huge essays takes sooo long

Anyways, yeah that's about it! Now I have the text scaler!
Also, there is this really stupid thing that even if I clear the buffer, running buffer.Clear(0xff), it doesn't really, and I still have to run buffer.fill(0xff)
Oh well, also two more notes:
- THE PCB IS DONE PRODUCTION!!! (Now all I have to do is wait for them to assemble it!)
- I noticed that I can arrange the screens in a better way that should look better, And thus I'm thinking of completely remodling my entire thing... I should get that done real quick so that its done by the time the PCB arrives and I can just go print it and worry about debugging my PCB
NOTE: As you can see, this is a really long journal, and really long journals take a tonne of time to get done on their own ...
Madhav ๐
added to the journal ago
Tested (& Received) E-INK DISPLAY!!!
Firstly, I received the displays!!!!!





That last one might have looked weird ... that's my E-INK!
It was actually a pain to get it working. First, I connected it to my rpi Pico's wrong 3V3, so it wasn't getting power, then the library was really weird and glitching out. So, I changed my library to Waveshare's official one, however I messed up and installed the 2.13 inch display one, not the 2.9 inch one, so again it didn't work.
In between, I had messed up with which pins were SCK and MOSI, and kept getting the Bad Pin error, so that definitely wasn't nice.
All in all, it's really great it worked, but it took too much effort ... Hoping the LCDs are easier!
Madhav ๐
added to the journal ago
Added Another FPC Connector
This is basically just so that if in the future I want to have it connect to a daughter board, it will be able actually communicate. Because I didn't have access to STM32CUBEMX, I had to look through the datasheet to find out which pins (of the remaining) were the most capable so that I could actually use them. I settled on this setup:

Note: STM/ESP32_SCK is actually PA1
So now, I was left with the daunting task of finding the ideal order (shown above) so that it would actually be possible to route. On my 5th revision, I found that.
Now to actually wire this monster up

As you can see, the majority of the lines look alr, and I might actually just change the two weird ones (on the bottom).
The biggest problem, however, is the fact that this is already the most dense part of the board, where the four layers are most full, so wiring an addition ~16 lines is not going to be fun. Thankfully, if absolutely need be, I can always use inner 4, which is supposed to be GND only...
Some Time Later
Ok! I think I've settled on this setup for the pins:


Now time to route this all...
Routing More double checking!
Here's (hopefully) the final thing.


Now it's time for some routing
Routing
(some time later)
Ok so here's what I have!

This is one heck of a dense board, with all four layers being quite well used: (L3,L4,L6)



Not gonna lie, but that was quite a bit easier than I was expecting. I did end up changing a bit of the schematic, so that it would actually be nicely usable, so here it is!

(Mostly just switching around a few pins to avoid loops)
Yeah well that was a full days work! Proud of what I got done.
Oh yeah, btw re-added the suture vias (the ones that connect between layers)
ALSO I NOTICED THAT ALL MY JOURNALS FROM THE PAST DAY ARE ALL GONE?????????????????????????????? Like 10 hrs of work dissapeared!! Just a glitch
Madhav ๐
added to the journal ago
Artwork & Suture/Stiching Vias

First things first, I quickly fixed the artwork on the back side so that the hackclub logo wouldn't have lines going through it

However, on the front side I got feed back that the silkscreen was just a bit too much, especially with the ENIG art, and thus I removed it, even though I had moved all the designators...

The hard part
So that was all good, just a bit of easy artwork. But, the stiching vias were really bad this time, as for some reason the DRC was allowing the vias to be placed too close to each other, and then flagging them. So, I had to go through and remove each violating via by hand... (Some 50~60 vias)

btw my board now has 3000+ vias!!
Anyways, after that, there was the headers for the speed and 2 IO (buttons, etc) that were sort of conflicting with the battery, so I re-arranged them.

Debug Header
Reddit said that I should add the NRST pin broken out on the debug header, so that's what I did.

Required some wiring, including some on the bottom layer. (Which is my mostly empty one)
Madhav ๐
added to the journal ago
Designators
I moved all the designators on the top side so that they are readable with the new wavy lines. The backside will be done tomorrow, when I redo that silkscreen to not conflict with the hackclub logo.

Anyways, there just was this one spot with the microSD card where it was tough, but most of the other places were easy enough

Madhav ๐
added to the journal ago
Artwork
I added a bunch of new art, fully custom (except for the hackclub logo)
It took soooo long because in the beginning I was going to make a single PNG and import it, but I just ended up importing individual files and putting them in the right spot. It was just too hard to have one.


I'm going to clean up the backside a bit, but first I have to move all the Part-Identifiers so they are legible over this new silkscreen

Madhav ๐
added to the journal ago
Rerouted a bunch of the bottom layer
Basically, I want to put art onto the bottom layer, and thus I rerouted many signals so that the bottom is just mostly ground, on which its far safer to remove the soldermask

As you can see, there's only a bit of routing on the densest part of the board, but other than that its all gone!

Now time to actually get the art going!
By the way, most places were relatively easy, just switching the trace layer, but others required quite a bit of change.

I also tidied up this corridor so that the majority is in line with itself and thus doesn't affect the ground stitching around the ground plane too much.
Madhav ๐
added to the journal ago
Bought some stuff & checks and stuff
Mostly just double-triple checked stuff and bought it!

So yeah, the thing is that one of the things was from a blocked category, so I had like get @jay to unblock it, but then he had to re block it, but the problem was I had 2 orders, so he had to do that twice...
Well anyways I probably did some other stuff but I forgot it all so...
CAN โก๐
approved The Ultimate Bike Odometer ago
Tier approved: 1
Grant approved: $348.00
Awesome project
Madhav ๐
added to the journal ago
Wrong image previously
chaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrsssssssss.

Madhav ๐
added to the journal ago
Just a note
Umm yeah for some reason I went through the jlcpcb checkout again, and the cost went up by $10???
I'm really confused rn...
Well um i need characters so!

Madhav ๐
added to the journal ago
Compass is in stock!
Previously, the main compass of the board was out of stock, and the pre-order was showing inflated prices, thus I am able to save about $6!!!! Yippeeeee! I'm also thinking of adding the battery guage ic known as the MAX17048
My compass setup:
Madhav ๐
added to the journal ago
Some Basic Routing Fixes & STM32
So firstly I spent like a bajillion years on trying to figure out how the STM32 should be set, and just ended up adding a debug header, alongside a pull up on the NRST pin. The debug header made really good use of the mostly empty 4th Layer.

In addition, I also cleaned up the area around here:

So that there are only 3 layers being used, to make it so that the fourth layer is as much ground as possible to make sure the antenna is really, really happy.
Madhav ๐
submitted The Ultimate Bike Odometer for ship review ago
Madhav ๐
added to the journal ago
Finished Everything Up: Made it Submission Ready
Worked On the BOM, got all the prices, simulated checkout.

Also got the Github Repo looking nice. I have the CAD, with the F3Z file and the STEP file. I also have the Production PCB stuff with the gerbers, BOM, pickandplace and the .EPRO for the easyeda project.
I also worked on the Firmware, which took FOREVER as it is like impossible to figure out how the STM32 stuff works. I think I have the basic code in the repo, but idk.
the VAST majority of time went towards the firmware and BOM. Checking everything out take a bunch of time...
Madhav ๐
added to the journal ago
3D animation
I got this fire animation to render! it took over 21 hours! that was absolutely crazy and actually cost me a significant amount of electricity.
Here's the render!
You can also see the PCB now.

Madhav ๐
added to the journal ago
Photo Realistic Renders.
As anyone with any blender knowledge will tell you, blendering is crazy time intensive. Especially if it's your first time. After about 8 hours of work, here it is:
A photorealistic shot:

But of course, i have more for you!
So in class, we had an animation unit, and I got the basic hang of how animation works, and I implemented that into a setup on blender. Here's the stuff! (I did mess around with the lighting, animation, and background so it looks better. First, some shots:



And here's the video if you can access it. It has no sound btw.
Madhav ๐
added to the journal ago
Finished the Case!
Ok firstly, I did this work yesterday, but i grinded too late so i couldn't journal
Screws
screws, screws, screws.

Basically, I just added a bajillion screw holes with counter-sink. took me way too long, but hey.

Bike mount
I went outside and measured the size of my bike bar, and modeled around it!



in all:

Madhav ๐
added to the journal ago
3D modeling
I finished the base stuff!
I just have to add some screws so that the main truss can connect to the other parts, and so that the top plate can do so too.

Finished case:



Some inside shots







It was pretty simple of a thing ngl, but I still had to figure out how to split stuff. Next up, the screws & mounting to my bike. After that I'm going to speedrun some software, and try to submit this!
Madhav ๐
added to the journal ago
Vias, IMU, and Costs, and Impedance Matching
I don't really know how much I talked about Impedance matching before, but that's first up!
Impedance Matching
So like I sort of messed up the previous impedance matching, as it was accidentally set to the signalling on layer 2, not the top one. That was a relatively quick fix, but I had to double-triple check that it was correct, and then double-triple check that the stackup wouldn't mess with anything else.

I also had to re-place a bunch of vias around the line, as making it slightly thicker made it so that the vias were now too close for DRC to like.
Suture Vias
Y'know how before I talked about via spacing, right? Well screw that I'm not going to be putting a via every 0.6mm. Instead, only in the GPS area, I will have some 0.7mm spacing,

and everywhere else I'll settle for just 0.2MM spacing. 
Again, this was with the help of @tty7
IMU
For whatever reason, JLCPCB chose to make the IMU I was using into a standard assembly part,

But lucky enough for me, my friend @tty7 was able to point me to the ICM42 series IMUs, in which by random chance I picked one with very similar functions, and the exact same layout. All I had to change was a single capacitor from 10nf to 100nf.
Cost
I know all of this because I just so happened to have been checking the total cost. All IMU mess was just a byproduct.

Here's the picture before taxes and everything. You should note that the PCB is $42, and assembly is $160.
Assembly.
Out of assembly, you can see that about 42% are one time setup fees, about 40% are the components' fee, and the rest 18% are just for soldering and double checking the solder is good.
note.
It should also be noted that the fee is without any coupon being applied and without shipping and taxes
Lora
Yep. I spent some time contemplating whether or not putting on lora is a good idea, especially as a low-operational-cost alternative to cellular, if it even works.
But I sort of came to the conclusion that it wouldn't be worth the cost, as I would also need a receiver.
Cell RF.
Well yeah that was close. So mainly that that came out of my conversation with @mpk was that I needed to change my trace widths, but not by a ridiculous amount. For the coplanar part, that is.
So basically I had to change this wire to 3x its size, ~0.11mm to 0.35. Well that is absolutely huge, but other than that, I only really had to move the matching circuit and all of its vias in line,
and also put vias all around the board by hand to limit the board doing funny stuff, and I also had to recalculate the coplanar wire parts, several times, because it wasn't matching Max's calculations, and then I had to look up the prepregs' dielectric constant to see if that was the problem, and then I just ended up seeing the difference with my calculations and his, but all in all it was a small 5%.
Yeah by the way that was sarcasm. It was a metric tonne of work. AND i also forgot to do any work for my other duties...
Madhav ๐
added to the journal ago
Stuckification
I'm sort of stuck rn.
Like i don't really know how to build the other part of the enclosure, especially because I don't really know the dimensions.
Let me explain.
So basically, here's what I have so far.
BACK:

FRONT:

The thing is, for strength reasons, it makes most sense to machine the plates, however the machined items should be just that: plates. I want to make this entire thing as cheap as possible, so I am sort of confused.
So here's the thing:
I have a plate.

But I don't know the size of the E-INK. that's ok, i can put that in once I get it.
The problem is here:

I need a way to basically just attach the bottom, the middle plate, and the top plate together.
I am thinking that I could just extend the plastic cover further, however that would end up making the vast majority of this out of a weaker material.
I guess that really is my only good option...
Yeah. After some thinking, I think I can actually make this work.
hmm...
Yeah I think that I can make it so that the plate will attach to the PCB shell as it is, and from further on top it will just be a shell thingy, as its only a bit taller, about a CM. Once the main part ends, then the top plate will be attached to the top.
I still don't like this...
Madhav ๐
added to the journal ago
Added Screw Holes to the Top Mount
Yep. Here they are!

(just in silver instead of black to make them easier to see.)
Because they are designed for M2.5 screws, how it works is that the very top is 2.6mm, and as the screw is rotated, it ends up being able to tap itself in the plastic. To keep the grip, the hole becomes smaller as it goes, all the way to 2.4mm.


But yeah it took me like forever to confirm that this is ok, and I might have to redo this all anyways...
Madhav ๐
added to the journal ago
Standoffs, etc.
I 3D modelled in some mounting points for the PCB, so that y'know it can be mounted. I am going to add some holes, and well yeah.


Basically I just sketched some holes, and made the stand offs 8mm wide. I am now going to add the holes that will allow the not-self-tapping screws to self-tap themselves. (btw I've done this before it actually works).
Ooh, yeah I also made the back side look absolutely spectacular, including some really big fillets!


Once I add the mounts, all I have to do is add the bottom cover and find a way to connect everything!

Madhav ๐
added to the journal ago
Moved Header In by 6mm
I rewired the entire header part, and I also moved it in by 6mm, just like that that the title suggests:

It also actually simplified wiring, because now the wires could go from the right side, and thus I have to use one less vias.
This also allows, as I said earlier, for me to condense the entire thing!

(I'm sorry you're gonna have to compare it with one of the earlier images, as I really can't get fusion's timeline thingy to work with me.)
Madhav ๐
added to the journal ago
Top Bracket.
So um yeah that's about what I worked on.
Firstly, I had to think about the dimensions I was going to set up, and here's the sketch I settled on:



Basically, it looks like a big block, except for the fact that there is a recession in the center for the PCB.

However, I then noticed that it looks quite beefy, too beefy, and saw that the biggest part, by a very significant amount, was the screw terminal:

Thus, I went back to my PCB to change the mount, but then, ofcourse, it took me like forever to find any decent part, so yippee. :(
While I was re-rendering the 3d model, I noticed a switch didn't have a model, so I went looking for a new switch, which again took so long that I realized it wasn't even important :(
Now, all I have to do is change out the PCB model for the new one, and I'll shave some ~5~10 mm.
Re-aligning the new PCB was a real easy job, and I have an updated board now! only real difference is that there is now a switch to control GPS power, and a significantly smaller header.
btw, the header is used for connecting the wheel-rotation sensor to the ESP & STM, and can act like a ultra-slow-speed back-up comms line.
5mm thickness saved!


Just one note, I am thinking that I will move the E-INK header inwards, so that I will be able to have a significantly compactor case.

Madhav ๐
added to the journal ago
Fileting!
This is another task that should be the easiest in the world, but Fusion is being a donkey.
See, the outside fillets were easy-peasy.

But these donkeys thought it was a good idea to keep every small face you make. So, on the inside, it was a hole other story. (get it?)
At first these lines on this face don't look like a big deal, but drum-role-please ... they are!

When you zoom in, to fusions max, you get this:

Somehow, those two plates are separated by 0.002~3 mm, and I was unable to actually fix that, so I guess there's not going to be any filleting there :(( (fusion only allows me to move the sides by 0.001 mm at a time, and seeing as the difference is like 0.0024, I can't do anything about it. What a waste of time :(
Madhav ๐
added to the journal ago
SCREWSS!!
Do you love screws? I sure don't
Like bro I would have thought someone would have designed a better way of doing all of this. I just had to resize all my holes, and then on top of that because the screws are tapered, I had to taper the holes.

That might not seem like a big ordeal, but the thing is for a noob, you have to account for all the extra work of figuring out how to make the tapered holes, and understanding whatever the heck this means:

Oh yeah also I forgot that you know, fusion is like the weirdest software, completely counterintuitive, so that doesn't help. (Not as bad as Adobe though)
Oh, and here's some pictures:



Madhav ๐
added to the journal ago
Screw
After a (relatively) long break, I am back at it again!
My goal right now is to speedrun to protective shell around the PCB to prevent any harm. As there is sensitive RF on the board, unfortunately I will have to 3D print it, and CNC is out of question.
Ok. Its sort of time to get serious, as I am nearing the submission point of my stuff.
Screws
First up, it is really important to find screws that will, you know, actually work.
So first up, I took a look at the official datasheet for the LCD screens.

Basically, all it really shows is that the PCB to top of the screw hole is 4mm,

and the screw size is an M2.5.

I found some M2.5x8mm, M2.5x10mm and M2.5x6mm screws.
the thing is, though, that I also need a screw bit to drive the screws, as they have something like a T8 head or something, so that's that.
And even better, according to the Waveshare Docs, the E-INK also uses the same size screws too!
However, as I don't have the 3D model or anything, designing a proper size case to protect the screens in the event of theft/crash I might have to get the screens first...
All in all it took about 40 minutes to look for this information and just start the 3D modeling.
Main Truss.
Ideally, the main truss will be built from CNC aluminum, and that that you see below (in red) is what it looks like.


This side will be for the Screens:

And this side for the main board.

It is significantly higher up because of the huge batteries that will be in between the board and the screen, and thus it acts like a spacer. I am trusting that the aluminum is strong enough.

One (not at all) small thing is that I have to deal with the cost.

$40+ for the part, plus HST, plus shipping will add to quite a bit. So instead I am going to make the PCB mount to the top cover, which will mount to the main truss and the bottom cover. Only the PCB cover (the top cover) will be 3D printed for RF, but the others are all CNC.
Apparently, the fabs don't like all these weird extrusions. Who would have thought...
So I guess that all the work I did today was for absolutely nothing (not really the screws and stuff were useful but still). How terrific. I guess.
Well mounting hardware deleted!


Hey at least its colourful now!
By the way, thanks to @Raygen Rupe for helping me in getting the main mounting stuff lowered in cost and stuff. But yeah a big bummer that I have to redo this :hs:
Madhav ๐
added to the journal ago
Minor Power Consumption
So y'know how I said last time that my MOSFET's leakage current was like 15uA, and the module's PSM (Power Save Mode) draws just 3.2uA? Well yeah so I just decided to replace it with a switch, just in case I ever want to turn it off. (I don't know why I would, but still)


Side note: I still have trouble believing this small red switch is rated for 5A...

This way my total current draw (in max sleep mode) is now at:
| Device | Power Consumption |
|---|---|
| GPS | 7uA |
| Cell | 3.2uA |
| STM32 | 640nA |
| ESP32 Bypass1 | 100nA |
| Quiescent current | 75nA |
btw this took a bunch of research for new, updated numbers.
As you can see, the biggest power consumption comes from the GPS, but it would have been the MOSFET. Whereas the current current consumption is 11.015uA, the previous one would have been about 22.8uA, which is significantly higher, and had added points of failure.
With these numbers, we get a standby life of about 21183.235 days, or about 58 years on a full charge. Which is of course, honestly, CRAZY.
Madhav ๐
added to the journal ago
A little bit of power consumption metrics
I was just wondering about how much power the SIM7080G uses in its different modes, and a quick search gives the following: 
(Actually it was not quick because of how dog-water google has become)
Basically, when its idling it pulls 10mA, basically nothing for my on-mode, and when it's ready to shut up and sleep it draws just 3.2 uA, less than the GPS's backup mode (for its RTC) and way less than the MOSFET's leakage current of 15uA.
umm i also just forgot, when i came to do another journal i just saw that i was midway through my last journal entry so like umm...
Madhav ๐
added to the journal ago
3D Modeling, but slowly enough you'd think I'm a sloth.
Yep, I did 3D modeling.
Yep, I was slow.
Yep, you might wanna call me a sloth.
My model

As you can see, I have some stuff done, and the biggest part is that I have the main structure that carries the screen.

As you can see, it is a little complicated, but obviously doesn't have fancy fillets or anything. Firstly we have screw holes to mount the screens to it, and we also have the main structure to support everything. It has a hole in the center to allow for the batteries to poke through.

Of course it'll be more polished, but that's how the rough outline goes.
E-INK
So yeah I got an email back from waveshare, and they said they didn't have any more accurate drawing. Well that sucks, but I found a 3D model that was a case for one of those displays, at https://www.thingiverse.com/thing:4639641 In the example pictures, they showed it looking like it was almost perfectly a container, but I think I am going to have to put on the final touches at the end, once I get the E-INK and can actually measure it.

Color & Other
Color
In other news I figured out how to add color to the objects I model, and I also spent a while trying to figure out the optimal setup.

Assembly
As of now, I have it setup that the PCB is on top, then the holder, and then the batteries and board on top of that assembly. Next up I am going to design something to go under, for the plate I designed, and then something to go on top.
As my plan is, I am going to have 3 pieces, that will probably be screwed in place or something.
Deletion
So yeah somehow a bunch of my work got deleted midway, and then when I tried undoing/redoing absolutely nothing happened, but it wasn't a huge amount of work, so not a huge deal.
Madhav ๐
added to the journal ago
3D Modeling

Onto 3D modeling, I finally got started on it.
Basically, as it stands right now, I got everything loaded up into Fusion, and sort of got back into the hang of things.
LCD
I found an official 3D model of the LCD on waveshare's website, and here are some pictures. (It did take some digging, as I originally searched on other sites, like GrabCAD and Thingiverse. It was just random luck that I happened to go to the wiki, and that I just happened to find the model.)



PCB
I just quickly imported the 3D file from EasyEDA, and then realized I hadn't added any mounting holes, so I added those in the weirdest spots.
One is dead center-top,

And the others are on the very bottom, as you would expect.

Here's the model in Fusion

E-INK
I was sadly unable to find a 3D model of the E-INK, not from waveshare, nor online, but I have sent them an email.
Instead I found this case thingy-magig, but it's not any good :(

It's actually quite weird.
Anyways, I just made my own sketch of the suggested outlines, and well, there it is. Its not eventful or anything, but still:

All Together
Here is what it looks like as of now, with the PCB going to be on the top, then several ground planes are going to protect the GNSS antenna from the noise of the LCDs, and the screens on the underside.

I am working on a truss to hold the screens in place, and it's going to be like the blue thing and slot into the main thing. I am thinking I might get this machined so that it is ultra strong, as necessary for a theft-proof device. However for testing, just a 3D print should be alright, and then I can see if there is a future hack-club thingy in which I can design a unbreakable case. TBD.
Well yeah that sums about all that I have done in terms of the 3D models.
Madhav ๐
added to the journal ago
Suture Vias (Stitching Vias)
I worked on the stitching vias that basically connect the layers together. It was a bunch of checking what sizes are alright. At first I tried guess and checking. I started off with 0.8mm via spacing, and for some reason everything was glitching out like crazy, but eventually they applied.

Then, I tried 0.7mm, 0.65mm and then 0.6mm. They all worked โ however it sort of felt weird. Thus, I went googling what JLCPCB can produce and those clearances, and it hard core hallucinated. It said some crazy numbers, but it also gave JLCPCB's specification website.

From there, I had to go into the drill section, where it said that the via holes should be about 0.2mm apart:

But that feels like its way to close, as some existing vias I have are 0.305mm apart:

And EasyEDA doesn't allow vias to be any closer. But, sticking to what JLCPCB said, I changed the Design Rule โ and thus the DRC (Design-Rule-Check) โ to lower the spacing a bit, and MESSED EVERYTHING UP...
Yeah that's something I'd rather not touch again, but it still sort of works, and I just didn't bother changing the hole-to-hole spacing (needed to be >0.3mm to >0.21mm) and well yeah just figured 0.6mm spacing was alright.
So yeah, all in all my vias are about 0.6mm spaced, however near the PI network near the antenna, I have a denser grid of a basically hexigonal layout:

Madhav ๐
added to the journal ago
Replaced Shunt
Yep, I just simply did some calculations, and my current shunt was way to small, just 10mฮฉ, and I replaced it with a 20mฮฉ one. That means the maximum power went from <8A down to <4A, and that was enough to sort of concern me, especially with capacitor inrush and stuff, so I just checked around and stuff and I eventually was just like bruh whatever. The thing is, 4A is sort of close, as the SIM7080G can peak at 2A, and the displays could probably hit 1A, but I don't think the rest of the stuff will be able to hit another 1A.


BTW the shunt has the same package, so all I had to do was find a good one.
The shunt is +- 1%, so the my accuracy is only +- 0.04A at full power, but at low power (<1mA) its the INA226's 16-bit ADC that's the limitation. Thus, I changed the shunt to a higher resistance and higher voltage drop one so it can make out the difference easier. (It effectively adds another bit)
Accuracies
first number, current consumption.
second number, accuracy (either the ADC doesn't have enough bits/resolution, or the shunt isn't accurate).
@4A -> 40 mA (1%)
@1A -> 10 mA (1%)
@100mA -> 1mA (1%)
@10mA -> 100uA (1%)
@5mA -> 61uA (1.2%)
@1mA -> 61uA (6.1%)
@250uA -> 61uA (25%) (low-power operational current)
@100uA -> 61uA (61%)
Notice that creep? +- 25% is a lot at operational current
Madhav ๐
added to the journal ago
Just some pictures
I didn't have any good pictures last time so I am going to add them now.




I also cleaned up the layout of the huge 100uF caps and made them closer to it


Madhav ๐
added to the journal ago
Double Checking ... A lot of double checking
Yep just as the title says, I did a bunch of double checking. The two biggest things I found were that
- The compass wasn't connected to power, and
- The main Cellular power switch wasn't hooked up right.
In more detail:
Compass
So if you look closely at my last shot of it, you'll see its hooked up to the LP_3.3V line. However, for whatever reason, it wasn't saved when I came back to it today, and well, yeah that would be bad. So, after adding the power lines, here it is.

Main Cell Switch
So remember this part of my schematic?

Yeah, as you can see, the high-side MOSFET will switch the power on and off, as long as you provide it a signal. See that? You need a signal. Well yep clearly, I thought there would just be some magic and it would just work.
So well yeah I just added a connection, to pin 6, (IO6) of the ESP32
Power Saving
So you have to think, what about power saving. What if I don't want to change my batteries out every week? Well then in that case you have Power Saving!
So yeah instead of having a main MOSFET, with leakage currents at ~15uA, a lot in this case (total low-power power consumption is ~252 uA) I am going to have the power be delivered from the GPIO of the STM32.

like this ^
One last thing, to have the STM32 in ultra sleep mode, I am going to have the set up be that the Hall-effect sensor is (for bike speed measurement) is going to be on a wake pin, so rolling the bike should wake the system up from its ~25 uA sleep setup. (BTW at this consumption it should run for a few decades!)
GPIOs

So yeah as you can see in that image, I am sort of out of GPIOs on my ESP32S3, however my STM32u575 still has 36 / 82 IOs open.
I also added a screw terminal so that I can just screw in the wires that connect to the wheel's magnetic sensor.
Feedback
Not only did I upload my entire schematic to Gemini (only useful for finding the BMM150 (compass)'s lack of power), but I also uploaded it and asked my friends who have used the STM32 also, one of whom is using almost the same chip. (STM32U575 vs STM32U585)
The biggest thing was the power consumption that @tty7 helped with, and @NotARoomba helped a bunch with helping me get started with the STM32 and looking over my schematics.
Note
I really have to get going with my 3D modeling, but I've heard that Blueprint will be extended to Jan 31st...
Madhav ๐
added to the journal ago
Finished Designing and Routing the PCB
I get it ... the title is too long.
Pics!
So yep here is my PCB, all done!

It looks way more impressive when you take a look at this picture:

Note: I am still going to add the stitching vias at some point.
IMU
I wired it up, checked datasheets, got Gemini to check it, and got this nice guy, @tty7, to help me check it out.

Compass
For future in-built navigation, and just a cool feature to have. I had to go look through a bunch of options on JLCPCB, and this is the first one that came up that was supported by economic PCBA, and also was compatible with the STM32. It took me a while to see that it was actually compatible, as it said 0.7V, and then real small VDDIO. Basically that means that it drives high at 0.7 x VDDIO, not 0.7V. (STM32 needs ~2.3V)

Another cool thing is that it uses BGA, but because of that I had to use a tiny 0.08 mm trace, and cut-down the Design-Rule clearance, hoping nothing bad happens... (The two decoupling caps are bigger than the entire chip!!
Headers
I added two (2) headers, one for the hall effect sensor that will count the amount of times the wheel rotates, and one to hook up the E-INK display.

I know this is rushed, pls forgive me.
Madhav ๐
added to the journal ago
Just a bunch of Finishing Touches.
I think I am going to try a new type of journaling.
Basically, I am just going to tell you what I am doing, add pictures as I work, instead of doing it afterwards.
Re-Check
I was just checking over some schematics, and voila! mistakes!
Basically I just completely forgot to add the VDDA wiring, basically the power line for the analog sense circuitry.

I have now added the 4 capacitors it wants.
I also wired up the "Power Good" pin, which basically just monitors the ESP32's power supply to see if it is overloaded (should never happen) or is giving messy power. I could temporarily turn off the backlights or something to let the power line re-stabilize.
LEDs
As the stuff is coming together, I worked a bit on LEDs, I added one on the ESP32 side, for power, and on on the STM32 side, this being just for indication and debugging. The biggest problem with adding additional stuff right now is that it makes everything much, much harder. Like to add the STM32 LED, I had to try so many different places, and eventually settled on this one:

When I added the "Power-Good" wiring, and the "Power-Enable" wiring (to control the ESP32 regulator) I had to put in many more vias, etc, because as the wires went, they broke through the invisible ground planes, on top of everything else...
Ferrite Beads
After a bunch of help from @tty7, I decided to add Ferrite beads to the USB ports to reduce their antenna-like properties. (L6 btw)


IMU
I also got help with finding an IMU to detect when the board is shaken, maybe to remove it during a theft or if the entire bike is being stolen, and it's the ICM-42670-P. (Still have to wire it up tho...)

Note:
Tomorrow i might, after finishing the IMU and Compass, go do a quick run of the 3D model. Due date is approaching quickly...
tty7
gave kudos to The Ultimate Bike Odometer ago
this thing is really complicated and I have no idea how you managed that much rf stuff without going insane
Madhav ๐
added to the journal ago
Double checked costs.
Just a quick check.
Here's the cost of the PCB:

And including everything:

I am just really worried about clearing customs, and also paying the local GST.
Madhav ๐
added to the journal ago
Main Display SPI & STM32 Power
Yep that's all I did. Byeeee...



Still here? Well in that case, Let me elaborate, for you my highness
Displays
It was a good idea that I started on the displays.


The fattest of these ribbons are actually there just because of the display. The green was is for the 5-point touch on both displays, and the red wires are for the (hopefully) 80 MHz SPI bus.
To run this ribbon, I had to rewire so many things to make space.


Like the slower SPI bus here. For the microSD card line, since it was also really high speed, it couldn't have any vias.

That's why this area had to be rewired to move the Power-Generation circuit over.

Even then, the slower parts had to jump under the main, high-speed, signals.

I know this doesn't seem like much, but when half the board is already done, wiring even a little more can require changing a lot of stuff.
STM32 Power.

I put it this Buck-Boost converter here. This converter, somehow, has a Iq of just 75 nA! That means that only 75nA will be used when no power is being drawn by the STM32. (That was the biggest reason I chose this setup and buck-boost, even if it was a bit more expensive.)
I also added a MOSFET to control the supply of current to the low-power electronics, like the INA226 and the (tbd) IMU

Madhav ๐
added to the journal ago
More Routification
Again, I'm sorry its getting very late. I hard grinded...
Current Pics
Anyways, lets start with some cool pictures of my current setup. You'll notice a hole lot more stuff.


Stuff I did
Basically, I just worked on some ESP32 stuff, and the foreign language of the STM32
ESP32

Simple enough, just wired up the UART connection, and the SPI connection, both to the STM32. They are just some data communication lines.
STM32.
This was the big boy today, with a holy lot of new stuff. Before, there was just the crystal and some decoupling caps. Now:

You can see that there is the COM lines, and also a thing called an SD card. Yep, I wired that up, and that took a lot of time because apparently SD Card lines are sensitive. (Oh yeah also I had to manage vias, almost went on to the fourth (and final) signal layer, and that stuff.
SD card

Here's the SD card slot, and yeah. Last time my setup didn't work, but I hope that with the pull-ups and the 33 ohm series resistor it will be ok, even if it has to be slowed down.
The SD card did take by far the most amount of time as it has to be wired so sensitively and has 6 wires, all so special...

Final note.
I have left out the boring and repetitive stuff as I really need to get to bed and stuff, but yeah I think you should get the gist of it.
I am also going to work on the main display SPI as it is important and high-speed... (tomorow)
Madhav ๐
added to the journal ago
More Routing & Schematics. Again.
Sorry for the rushed journaling Yesterday and Today.
Current Stuff


Power (esp32)
So yeah, I wired some POWERRRRR

Basically that entire setup took me like 3hrs to do, because that datasheet was just not being processed by my brain. It was almost like trying to get meaning out of a blank piece of paper. And then, after I finally got stuff, I had to find the proper inductor, placement, and all that good stuff. Y'know the stuff that takes you hours...

Power (Main)
So yeah, I wired some POWERRRRR
Here's the INA226 circuit, the device that will measure the battery voltage and current draw

This was the most straightforward thing I've done as I've wired this one up like 5 times before, but I still had to find the shunt resistor (10 milli ohm) AND the good wiring stuff, like Kelvin traces and stuff. All over hyped if you ask me...

Power (GNSS)
So yeah, I wired some POWERRRRR
oh i've got to stop that...
Anyways, I wired up this MOSFET:

And it will allow me to control when the GSM module is on and that good stuff, but yeah that also took like forever because I had to choose the right king of MOSFET (P-Channel was a whole lot easier) and I had to then find a suitable MOSFET, and wire it up... Takes time, I tell you.


I also added the simply boot and reset buttons for the ESP32, and its looking quite good. I just have to get some final connections between the Cell chip and the ESP32, along with wiring up the majority of the STM32, including the toggle to control the ESP32's power line. (btw it draws less then <1uA when it's EN pin is low!)
Sorry again for the rush, I've been grinding too hard clearly...
Madhav ๐
added to the journal ago
More Routing & Schematics
UPDATE!!
Here's my most recent pictures:



STM32
After a bunch of contemplation, I have decided to but the STM32 in this little corner all by itself:

But another problem, USB lines:

y'know how it needs 90 ohm differential routing and all that good stuff? well yeah that's why those wires look so weird.
Vias?
Another big no-no I have because of how the two signals need to cross over each-other. WHYYY?!?!?!?
Crystal
One good thing though is that my crystal wiring and stuff does look pretty good... 
GPS
I also wired up the entire GPS, did all the comparing between different models and stuff, and that.

You can see it with its keep-out and stuff. It's the L80RE-M37, and I still have to do the RX/TX stuff, but that's a quick 5-minute job.
...
And yep I just finished that too.
Display connectors.

Although not wired, I have found them a place
Challenges
Coms
My biggest problem right now is that I need 3x UARTs on my esp32, but although it technically has 3, I'd rather not use UART0, as it might bug-out.
So, instead of uart to talk between the STM32 and the ESP32, I could use SPI, but the thing is there is not a thing on the internet, after 1+ hours of searching, for SPI slaves in Micropython.
Madhav ๐
added to the journal ago
Some Display Planning.
Alright. Here's the plan:

Basically, the big green rectangles are the displays, LCDs on the edges, E-INK in the center. The other green rectangles are the 18650 battery that are going to be used. With this layout, we have the antenna kept as far as reasonably possible, but we still have a huge-ish area,

As you can see, it'll take up around 168 MM, in width, and about 110 mm in height. To make it symmetrical, it's probably going to go to ~180MM, which isn't crazy, but still.
Madhav ๐
added to the journal ago
e.g. Routed the PCB
Did it again.
More Schematics and Routing.
GSM (Cell)
I think I might have just finished the entire schematic & wiring for the GSM. I'll double check it later, but real quick:
1 - I wired up the status LEDs

2 - I wired up some HUGE caps


3 - Wired up a header so that no matter what I have the cell stuff to test for next time I do this

4 - Wired up the connection between the voltage shifter and the ESP32

5 - Set PWRKEY, the main power controller of the SIM7080G, up.

ESP32
As for the ESP32, it was just mainly me running a program on one of my existing boards with the same chip, and I found that only GPIOs 22 - 38 don't work (38 does, 22 doesn't)
I also wired up the USB line/port

LCD
Found some FPC connectors


and these are going to be the connectors that connect to the LCDs, as the LCD comes with a 18 pin FFC (like a FPC) and the LCD has a connector. This just makes the physical wiring job a bunch easier, as I now longer have to solder some 26 thin wires that keep breaking in my face. (Speaking from experience. Looks down. :SOB:)
STM32
Well I have a bunch of capacitors...

Need to wire that stuff up.
In other news, I sort of got the SD card stuff ready, and I prepped the USB port for wiring

P.S.
I really am glad I chose a 6-layer PCB, as I am already using 3 of the wiring layers, and it is essential to have huge solid ground layers for the antenna.

There's a reason why my board is shaped like that, and why everything is so clumped up. Maybe I should spread stuff up, but I sort of have to rush ... only ~8 days left to submit.
Madhav ๐
added to the journal ago
e.g. Routed the PCB
Always wanted to do that
Anyways, I got this level shifter part done:

Just don't mind the left side which still needs work.

But like anyways, this one was quite tricky because I wanted to keep those wired nice and tidy, and avoid vias to 1. disturb the ground plane, and 2. block the other 5 layers. As you can see, the left side still has to be done and I really do have to find a way of doing that...
BATTERIES!!

I also set it up to have the 18650 holders, as they consume the most space and block a lot of stuff, but also because they have to be relatively close to the GSM (cellular) module because they need some FATTT traces.

STM32
This to is going pretty swimmingly, as I have started on its schematic.

Just one hiccup, this STM32 requires a beefy capacitor at the VCAP port, but it is also required to have really low ESR at 3MHz, which is actually very hard to get. Thus, we have the option to trade the 4.7uF capacitor for 2x 2.2uF, as these are waaaaay easier to get in appropriate sizes, and the ESR requirement per capacitor halves or doubles or something just making it a whole lot more lenient.
Side note
I am a little concerned about how this all is going to be, as the space is there, just a little tight, but most importantly I still have to get the displays and stuff going, main power trees, and also design the case in <10 days to submit for Hack Club's Blueprint.
Madhav ๐
added to the journal ago
Routed, Re-Routed, and Re-Re-Routed the ESD Protection
Ok. I get it, that was a long title. And also I am sorry, I don't have as many pictures as I would have liked to have. Anyways, here it is, my current version of the ESD Protection.



My Reddit Post btw (it has some pictures from my last rerouting)
Explanation Time.
So basically how it works is that it just has some 22 ohm resistors near the module, really really close, and those are there to filter out noise, just like how it was recommended in the example circuitry.

Here, we're talking about R1, R2, and R3.

My design ^
Example Design v

ESD & TVS
Then, we also have a TVS. This can be seen in the reference design, just above, and also here are some close up shots of my work.
Here is the TVS protecting not only from ESD, but also capping the voltage first, by putting it into vdd.

And routed:

Curvy Routing
In case you didn't notice, I am trying to use curved routing for this stuff, as it basically helps with making the signals clearer, even better than just 45 degree traces.
As a bonus, here's my routing so far:

Note
Again, you might be wondering how I managed to spend so much time routing, but it just is a slow task for me, especially with the curvy stuff, and also I first spent a bunch of time trying to route it all on one layer, and in affect, I have that. I just moved the data line to another layer, ground in between, so that it could be protected and not have stuff induced into it. I only have a via for ground, and one for power, VDD.
Madhav ๐
added to the journal ago
Block Diagram!!
NOTE. I use OLED / LCD interchangeably, even though I mean this LCD
So yep as the name suggests, I am working on a block diagram to make my life easier. HERE is the block diagram's latest version, but as of now it is just this much:

It also got me thinking of how I was going to set everything up, especially in terms of power management.
Power Management
As it is right now, the esp32 consumes the following amounts of power:
| Mode | Power Consumption |
|---|---|
| Wifi Transmit | 180 000 ~ 240 000 ยตA |
| Active Mode | 90 000 ~ 130 000 ยตA |
| Modem-sleep Mode | 20 000 ~ 70 000 ยตA |
| Light-sleep Mode | 700 ~ 900 ยตA |
| Deep-sleep Mode | 10 ~ 150 ยตA |
| Hibernation Mode | 4 ~ 6 ยตA |
For reference, I am going to use 2x Molicel INR-18650-P28A.
This is a 2800MAH 18650 battery, and with two in parallel, I get 5.6 AH.
If the CPU alone is using that much power, I might be cooked. The screens will likely draw about 100 ~ 150 MAH. Alongside that you have the cellular module, GPS, IMU, and a whole other CPU, the STM32U575VGT6.
But the good news is that the STM32 uses just 19.5 ยตA/MHz, and if I just run it at around 16 MHz (which is about the clock speed of the Arduino uno) it will draw just 320 ยตA
Even at full speed, it will just draw 3200 ยตA or 3.2 mA
And if you were wondering how that is possible, very simply it was on purpose. I chose the most lowest power thingy magig I could find, and yippee! here we are.
So like the problem is, which devices should go with which CPU.
The ESP32 is less power efficient, however it is more powerful, has bluetooth, and a tonne of RAM.
And ofcourse, the STM32 has power efficiency on its side.
So here's my block diagram again:

Basically, my plan is to run the ESP32 when I am biking, and along with it the GPS, GSM, and the OLEDs. This way, all the higher power stuff will only turn on for shorter periods of time. Also, just because it makes more sense, I am also going to put the wheel speed & distance sensor connection to the ESP32.
On the STM32, I am going to have the lower power equipment thus the E-INK and motion sensors, but also I am going to have it be the main CPU that will always be on. This way, it will be able to control when to turn on and off the ESP32, and as the have a UART connection between them, the ESP32 will be able to signal its choice, perhaps after it is done with periodically checking if the bike has been stolen.
P.S. I accidentally added an hour last time, so I am counting one less this time.
Madhav ๐
added to the journal ago
Started Making the Schematic and Routing! & COST ESTIMATE
Halo World!!!
Yes. It is me. I AM BACK!!!!!
Enough pleasantries.
Ok, first up,
ANTENNA!
So like a bunch of time and research later, I have chosen an Antenna! I am going with the 1462000001, a 698 MHZ - 2.7GHZ antenna!
Basically, I just datasheet dived. Datasheet dived a lot, and like it seems pretty good! Gรฉnial! And like y'know if that isn't good enough, it is most efficient at the target frequencies, the most carried frequencies around me.
And, then, I found out about antenna matching.
Antenna Matching.
Like if someone just thought. "Hmmm. I think that being an RF engineer is a really easy job. Why don't I just through ANOTHER wrench into the process.
So like they just went and messed up with the antenna's frequency efficiency chart, and just did this:

This is basically just a graph of where the antenna is most efficient, and like the yellow line is with matching, and the blue without. Lower is more efficient. As you can see, the blue line may look just fine, it is just efficient at a different frequency.
BUT
the problem just is that that doesn't do much. You need the antenna to be maximally efficient at the frequency you're going to be using it at. And guess what? The majority of people will be using it at points where the yellow line if lower. So like y'know, why can't they just make the yellow line the default!?!?!?!?
My Circuit.
So like we talked quite a bit about that stuff, now let me show you my current schematic and layout.
Firstly,
We have this thingy:

That's basically just the antenna, with its ground, and the signal line. Then, below it, we have the antenna matching circuitry.

This is basically just what the datasheet recommended for a 130mm x 60mm board. And guess what board size I have? 130mm x 60mm. Yeah I'm not about to mess up with this stuff that is adding $50 to the cost!!!!
Current cost.
Speaking of which, here's what my current cost breakdown of just the PCB(A) is looking like:

I know bro. I'm already at $100, after using the $20 coupon for a 6 layer PCB, and after a cheaper form of shipping which will inevitably be more expensive for the higher-value finalized PCB.
And all of this is not including the $70~80 for the LCDs, and ~$30 for the SIM stuff, likely lower.
All in all, I'm already at $200, and really just need to make the rest of the PCB as cheap as physically possible.
btw, me asking for confirmation that my stuff was good to ~3hrs in and of itself

Madhav ๐
added to the journal ago
More Screeny Stuff
Yeah I sort of decided I was going to model the screens out and determine how I was going to set up the pcb. So I sort of have a few options, I can have a setup like this:

With 3 displays: 2x LCD + 1x E-INK, or
With 2 displays: 1x LCD + 1x E-INK.

3 Displays
So in the option with 3 displays, it has the pros of having a far more beautiful setup, as shown in the upper setup, and I will also have access to 1.6x more screen space, and double the colored screen space.
It would look something like this:

but like y'know, actually beautiful.
it would basically have the highlighted sections have digital dials, with numbers in the center,

And the center display would just have the battery and all the things that don't change very frequently, along with an analog clock so that I can sort of know the time, even if it only updates once every three minutes.
We could have the right display have the speed dial, but now that I think of it I don't really know what I would put on the left one.
I am thinking that I could have the speed dial maybe in the center, with the E-INK off to one side for all the static information, and I could have on the left side the other stuff that updates semi-frequently, like the distance travelled, the cellular status and all that stuff, other semi-useful GPS data, temperature, and all that other good stuff.
One last pro is that I would have more space to work with underneath the board, to try and get it as close to what the cellular system wants.
Cons
However, the main cons to this setup are the fact that the extra display costs an extra $20, and also that this larger PCB will cost more. (About $10)
All in all, this option will cost $30 more.
2 Displays
In the version with just two displays, I would have to cut back on the font sizes, and that would really reduce the readability of the entire system, and it would look a bit lopsided as it would have an unbalanced screen setup, but it would save the other $30
Conclusion.
As my STM32 chip has enough separate SPI lignes to handle the displays on individual ones, I definitely feel like the 3x display option is better.
And, as I (likely will) have 2x CPUs, I can make it so that the STM32 can control maybe the E-INK and the SD card reader/writer, while the ESP32, the one I am more confident with, will be able to drive the other 2x OLEDs.
EXTRA!!
And, as a bonus, I have this cool little surprise for you!
I finally have a rough cost estimate. We are looking at around US$130~150 for the PCBA, including the 6 layer PCB, while the extra displays will add about $80. The SIM card and data plan will be about $15, and all in all, add misc. stuff, and you get about a total of around $250~260 USD.
Madhav ๐
added to the journal ago
Antenna and STM32
Ok well yepee good news this time
GOOD NEWS FINAAALLLLYYY!!!!!!
In case you can't read, lemme say this again. WE (i) FINALLY HAVE SOME GOOD NEWS!!!!! (and its not even a little)
Thing 1:
I found a bunch of other antennas.
Yep. I literally just Google MOLEX antennas, and i just found 5 or so antennas that JLCPCB also just happens to have, so that's good.


I am thinking of using a low frequency antenna on the board and also a higher frequency antenna, but I am sort of scared of cancelling of signals or some other thing that could do more harm than good. I guess I'll see. I found this good link to combining antennas but again I didn't have too much time to read about it after learning about the STM32
Thing 2: (the last one)
I also sort of got somewhere with the STM32 crystal and stuff, and I am thinking I might start to place some components soon, but first I am thinking of how to set up the PCB, like I don't know what sizes and stuff will look good, so I am thinking of Fusion360ing a protype, a simple thing with proper dimensions for the screen so that I can determine the PCB's size, which will then give me the dimensions I have to work with.

Madhav ๐
added to the journal ago
More Stupidety.
So like remember how I was complaining about the previous antenna not being in spec and all of that stuff? Well I decided to find a new antenna.
The new Antenna
So of course, spending a bunch more time, I found a new antenna. It looked pretty solid, was $7.39 on Mouser, only a dollar more than my other choice, and best of all had spectacular low-frequency reception, with around 55% efficiency, and at the AWS band it had a really outstanding 75% efficiency.

Now like if you haven't detected the foreshadowing yet, it's alright. I get it.
FORESHADOWING
I think now you can easily see it.
I went down the rabbit hole of seeing if it was good and stuff, and finding out how to drive it, and then just as I was about to make it my final choice,
BANG!!
I thought of checking JLCPCB if they actually had it in stock. And of course, they didn't.

WHYYYYY
I had even calculated the impedance matching and all of that stuff :heavy-sob:

Of course I only chose to check after getting to the
th page....
Time to go see if there is another antenna i can use...
Madhav ๐
added to the journal ago
CELLULAR!!!!!
So yeah, um, as the name suggest I worked on cellular stuff!
So yep as you would think it really is a pain.
So like after reading the first 50 or so pages of the datasheet, I came to the point at which I decided it was time to work on the antenna for the cellular module. As of now, I have chosen to use the MOLEX 2047740001, a cellular SMD antenna, and like yeah it took me like forever to see if they were matchable.

And like that's where the problem comes in. It is alright, but doesn't cover the same bands as the module. The cellular module that I am using can handle the significantly more popular band at 700 MHz, however this antenna is unable to do so, so I will have to see if I want to just rely on the higher frequency bands, like the AWS band at 1900 MHz to 2100 MHz. This will, however, result in lower reception indoors, making it much harder than it has to be to test and debug.
Cellular-Indoor-Penetration-v2-OL
And like y'know the biggest thing I am worried about is if this thing doesn't work. I REALLY want this thing to work, and the thing is I am willing to spend the extra amount of time to do extra research. There is a reason that I spent so much time researching the STM32, and I might still chose not to use it as I really want this to work and I have experience with the esp32.

Well yeah that's about it. I also did spend some time looking into different antenna types, and I really did want to try to use a SMD or patch antenna.
I had tried looking for a guide to help me design a trace antenna, a antenna that is part of the PCB itself, but I was quite unsuccessful, and I really didn't want to make it the reason that my board failed. Like y'know if I had chose to follow a half-good guide, and it turned out that I messed up on the antenna even the slightest, then bye-bye $200 (more likely $250-300)
So yeah I then Googled SMD cellular antennas, and, believe it or not, I actually found something! This was like the first thing, so yeah, but I think I will take a look at other antennas too, as this has a low-frequency problem.
Madhav ๐
added to the journal ago
DISPLAYS!!!!
So like remember how I had sort of decided the display stuff and stuff? Well now for the decision . . . !
Display Decision!
So yeah here's my plan. I am going to use 2 displays, one LCD, and one E-INK
E-INK:

The E-INK display costs US$19
LCD:

And the LCD is for about US$17
My explanation
This is basically just so that on the most sunny days we (I) have the option to basically just rely on the (veeeery) slow updating E-ink, and on laterally every other day I can rely on the LCD. I was also considering having a heads-up display, basically attached to my helmet, so that I can also just like easily see how fast I'm going, even on a sunny day. This will add more cost thought...
Lemme just quickly draw you a drawing:
======================= . . . . . . . . . . . . . . . . ====================================
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|--------- E-INK ---------| . . . . . . . . . . . . . . . . |------------------- LCD -----------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
--------------------------- . . . . . . . . . . . . . . . . -------------------------------------------
bro that drawing alone took me 10 minutes because of markdown :(((
Anyways
basically I was thinking of maybe having some keys in the center or around it, along with buttons on the side. I am also very happy about the fact that the LCD has a touch panel, but in the winters it will be sort of useless. Don't worry, it doesn't get cold enough here for the LCD to stop working, though the E-INK might not enjoy being that cold... we'll see...
Madhav ๐
added to the journal ago
STM32
Alright. Here we are.
MCU time
The time has come. It is time to learn the STM32!!
So I spent way too much time again and chose a chip. So last time, I basically learned of my options, and I saw that the power consumption was reeeaally low. Like 16uA/MHz low. With a 4 MHz clock, that comes to just about ... 64uA. That's it. Less than 0.1mA!!

And it's not even that expensive! Just around US$5 on JLCPCB.
But now's the hard part. Learning a completely new thingy. A completely new chip.


Like that took me 409 replies to learn. 409!!! And if you look closely it took 2.5 hours just on the conversation, not including the stuff before.
And like if that wasn't enough, it is just so confusing that I got a headache and decided to try again tomorrow.
If you want to see the conversation for yourself, you can see it here: https://hackclub.slack.com/archives/C083S537USC/p1763230254755849
TL;DR:
Basically I just tried learning a completely new kind of microcontroller that was on a whole different level, and now I have somewhat of an idea of how to wire it and set it up, thanks to its datasheet, and a veeeerrrryyyy helpful person named Nathan Alspaugh, @NotARoomba
Madhav ๐
added to the journal ago
Display
I was wondering about what kind of display to use, and again, a low power option would be essential. I would also ideally like to use something that, y'know, can be read in the sunlight!!!!!
Thus, I have a few options. I could chose to realistically either use a TFT, AMOLED, or an E-PAPER.
It was quite a journey to find out more about power saving devices, definitely not helped by the fact that googling "OLED" gives you this:

Completely the wrong kind of thing. I had basically given up on finding OLEDs (after like 3 hours trying), when I found out that in the more niche space they were called AMOLEDs, and these things were sort of what I was looking for. What I am thinking of right now is to use an AMOLED or a really bright TFT/IPS LCD on the front side, so that I can have sunlight-readable colour text that can actually be updated at a reasonable pace, and also have an E-INK display on the back side to show battery voltage, etc.
And I got carried away again
Fell down the loophole again
And here I am, with a new idea. I was thinking of doing something like have a square(ish) display on one side, like an AMOLED, TFT, etc, and having an e-ink display on the right side for the battery gauge. I think it sort of is coming together.
I'm thinking of having an ESP32S3, with a STM32, Dual Display, and the cellular module I found, along with the simcard from simbase, if I recall correctly. This will let me basically take care of everything, and all I need is a GPS!
NOTE!!!!
Incase this feels rushed, you should know that I have spent like the past 15 days literally trying to find a good display, along with a good micro controller. I recently made a very bad example of how to save power with a previous miniature thingy-majig, and I don't want to do that again so I really am trying to be careful.
Madhav ๐
added to the journal ago
CPU
I've spent a lot of time looking at an ideal CPU to run on the board. So far, I have a little bit of experience with the esp32s3 and the esp32, however those boards no longer support assembly by JLCPCB, so there is a chance that I might end up wrecking the entire board in me trying to solder it onto a dense board. I would also like for this to be as dense as absolutely possible, so I would ideally use something smaller, perhaps like the bare chip instead of the WROOM module:
vs 
That basically looks like this:

So yeah, that's option 1.
OPTION 2:
I could also chose to use the RP2040, a very versatile chip that I have made a simple board with, and as it is a quite easy chip to work with I do quite enjoy using it. The only caveat is that it uses a significant amount of power, and I need a chip that can either always be on or that is able to turn off into a power saving mode, again a difficult task that would require a second chip if I were to want to pursue.

Another potential problem is that the RP2040 does not natively support wifi.
OPTION 3: STM32
I also have the option of running the STM32, a chip that I have to this day never used. It is quite a new thing to me, however I have heard that it can natively support wifi, and has ultra-low power modes, so it does seem quite attractive, except for, y'know, the actual having to learn a whole new chip, and work with that kind of stuff.

I have to say this does look quite powerful ^^
CONCLUSION
So yeah, all in all I have a few options, and actually now that I think of it, I will probably have to use "standard" JLCPCB assembly as I have LGA parts, the cellular module, and as it too has a low power mode it could very well work; the STM32 is also looking quite nice, and if I wanted to I could also chose to use a RP2040 along with a 555 timer or something like that that will automatically turn off the chip every few seconds, or as the microcontroller signals. I know adafruit has a board like that...

Madhav ๐
added to the journal ago
BRAINSTORMING pt3: An ACTUAL Idea.
Alright! I think I am going to surf off of the Wave Share Board's hard work, and I am going to try and copy some of its stuff. Firstly, I did some research, and I think that I can use this cellular chip: https://jlcpcb.com/partdetail/SIMCom_WirelessSolutions-SIM7080G/C2943992.
So then, I went through the datasheet, double checked most of the stuff, and it looks alright, though I am only on page 26/76 of the datasheet, so that's something...

Also the fact that it is an LGA concerns me a bit too...

Here's the datasheet btw: https://www.texim-europe.com/Cmsfile/SMM-SIM7080G-Hardware-Design-V1.04-DS-200525-TE.pdf
HOURS: 2.4 / 7.2
Madhav ๐
added to the journal ago
Brainstorming pt2: Blues & Waveshare Cellular.
I went around looking for other providers of cellular service, and I came across this thing called "Blues" and they also provide cellular and stuff. The one small problem is, after looking all through there documentation, the cost is still waaay too high:

So umm. That was a waste of an hour...
I also took a look at WaveShare's cellular offerings, and they took looking pretty cool!:

Just one problem. The documentation is really hurting my brain. I am actually pretty stuck because I don't know if I should just go for using Particle IO, as that seemed to be the easiest to set up, or I could go the polar opposite and completely custom design a PCB that has build in cellular. I sort of do want it to be custom, but it would be sooo much easier to use Particle's Boron...
A bit later:
Brooooo I am still stuck :((((((
I still have no idea how I am going to do this. Ideally I'd just make a board and hope for the best...
I am sort of leaning towards using a fully custom setup...
Hours: 1.4
(Journal Re-Upload, Messed up last one...)
Madhav ๐
added to the journal ago
BRAINSTORMING pt1
I brainstormed about how this project would go. As it stands right now, I have a few options, but I don't really know how to do it.
What I am thinking about doing is having it all in some king of enclosure, with a main board, GPS, and some kind of Cellular Module. The problem is, I need to have proper battery that has sufficient charge and stuff, and I need to find a good cellular module. I was thinking of having one PCB do it all, but I don't really know how I am going to do it.
I did some research, and I am thinking that I should use the module in this image:

But other options I have found are this thing called Simbase,

And I am sort of thinking of using it with a normal cellular module, maybe one from waveshare.
HOURS: 3.4
(this is a journal re-upload, I messed it up last time)
Madhav ๐
started The Ultimate Bike Odometer ago
10/9/2025 8:28 AM - BRAINSTORMING pt1
I brainstormed about how this project would go. As it stands right now, I have a few options, but I don't really know how to do it.
What I am thinking about doing is having it all in some king of enclosure, with a main board, GPS, and some kind of Cellular Module. The problem is, I need to have proper battery that has sufficient charge and stuff, and I need to find a good cellular module. I was thinking of having one PCB do it all, but I don't really know how I am going to do it.
I did some research, and I am thinking that I should use the module in this image:

But other options I have found are this thing called Simbase,

And I am sort of thinking of using it with a normal cellular module, maybe one from waveshare.
HOURS: 3.4
(this is a journal re-upload, I messed it up last time)
10/9/2025 8:30 AM - Brainstorming pt2: Blues & Waveshare Cellular.
I went around looking for other providers of cellular service, and I came across this thing called "Blues" and they also provide cellular and stuff. The one small problem is, after looking all through there documentation, the cost is still waaay too high:

So umm. That was a waste of an hour...
I also took a look at WaveShare's cellular offerings, and they took looking pretty cool!:

Just one problem. The documentation is really hurting my brain. I am actually pretty stuck because I don't know if I should just go for using Particle IO, as that seemed to be the easiest to set up, or I could go the polar opposite and completely custom design a PCB that has build in cellular. I sort of do want it to be custom, but it would be sooo much easier to use Particle's Boron...
A bit later:
Brooooo I am still stuck :((((((
I still have no idea how I am going to do this. Ideally I'd just make a board and hope for the best...
I am sort of leaning towards using a fully custom setup...
Hours: 1.4
(Journal Re-Upload, Messed up last one...)
10/9/2025 2 PM - BRAINSTORMING pt3: An ACTUAL Idea.
Alright! I think I am going to surf off of the Wave Share Board's hard work, and I am going to try and copy some of its stuff. Firstly, I did some research, and I think that I can use this cellular chip: https://jlcpcb.com/partdetail/SIMCom_WirelessSolutions-SIM7080G/C2943992.
So then, I went through the datasheet, double checked most of the stuff, and it looks alright, though I am only on page 26/76 of the datasheet, so that's something...

Also the fact that it is an LGA concerns me a bit too...

Here's the datasheet btw: https://www.texim-europe.com/Cmsfile/SMM-SIM7080G-Hardware-Design-V1.04-DS-200525-TE.pdf
HOURS: 2.4 / 7.2
11/13/2025 6:01 PM - CPU
I've spent a lot of time looking at an ideal CPU to run on the board. So far, I have a little bit of experience with the esp32s3 and the esp32, however those boards no longer support assembly by JLCPCB, so there is a chance that I might end up wrecking the entire board in me trying to solder it onto a dense board. I would also like for this to be as dense as absolutely possible, so I would ideally use something smaller, perhaps like the bare chip instead of the WROOM module:
vs 
That basically looks like this:

So yeah, that's option 1.
OPTION 2:
I could also chose to use the RP2040, a very versatile chip that I have made a simple board with, and as it is a quite easy chip to work with I do quite enjoy using it. The only caveat is that it uses a significant amount of power, and I need a chip that can either always be on or that is able to turn off into a power saving mode, again a difficult task that would require a second chip if I were to want to pursue.

Another potential problem is that the RP2040 does not natively support wifi.
OPTION 3: STM32
I also have the option of running the STM32, a chip that I have to this day never used. It is quite a new thing to me, however I have heard that it can natively support wifi, and has ultra-low power modes, so it does seem quite attractive, except for, y'know, the actual having to learn a whole new chip, and work with that kind of stuff.

I have to say this does look quite powerful ^^
CONCLUSION
So yeah, all in all I have a few options, and actually now that I think of it, I will probably have to use "standard" JLCPCB assembly as I have LGA parts, the cellular module, and as it too has a low power mode it could very well work; the STM32 is also looking quite nice, and if I wanted to I could also chose to use a RP2040 along with a 555 timer or something like that that will automatically turn off the chip every few seconds, or as the microcontroller signals. I know adafruit has a board like that...

11/13/2025 6:44 PM - Display
I was wondering about what kind of display to use, and again, a low power option would be essential. I would also ideally like to use something that, y'know, can be read in the sunlight!!!!!
Thus, I have a few options. I could chose to realistically either use a TFT, AMOLED, or an E-PAPER.
It was quite a journey to find out more about power saving devices, definitely not helped by the fact that googling "OLED" gives you this:

Completely the wrong kind of thing. I had basically given up on finding OLEDs (after like 3 hours trying), when I found out that in the more niche space they were called AMOLEDs, and these things were sort of what I was looking for. What I am thinking of right now is to use an AMOLED or a really bright TFT/IPS LCD on the front side, so that I can have sunlight-readable colour text that can actually be updated at a reasonable pace, and also have an E-INK display on the back side to show battery voltage, etc.
And I got carried away again
Fell down the loophole again
And here I am, with a new idea. I was thinking of doing something like have a square(ish) display on one side, like an AMOLED, TFT, etc, and having an e-ink display on the right side for the battery gauge. I think it sort of is coming together.
I'm thinking of having an ESP32S3, with a STM32, Dual Display, and the cellular module I found, along with the simcard from simbase, if I recall correctly. This will let me basically take care of everything, and all I need is a GPS!
NOTE!!!!
Incase this feels rushed, you should know that I have spent like the past 15 days literally trying to find a good display, along with a good micro controller. I recently made a very bad example of how to save power with a previous miniature thingy-majig, and I don't want to do that again so I really am trying to be careful.
11/15/2025 - STM32
Alright. Here we are.
MCU time
The time has come. It is time to learn the STM32!!
So I spent way too much time again and chose a chip. So last time, I basically learned of my options, and I saw that the power consumption was reeeaally low. Like 16uA/MHz low. With a 4 MHz clock, that comes to just about ... 64uA. That's it. Less than 0.1mA!!

And it's not even that expensive! Just around US$5 on JLCPCB.
But now's the hard part. Learning a completely new thingy. A completely new chip.


Like that took me 409 replies to learn. 409!!! And if you look closely it took 2.5 hours just on the conversation, not including the stuff before.
And like if that wasn't enough, it is just so confusing that I got a headache and decided to try again tomorrow.
If you want to see the conversation for yourself, you can see it here: https://hackclub.slack.com/archives/C083S537USC/p1763230254755849
TL;DR:
Basically I just tried learning a completely new kind of microcontroller that was on a whole different level, and now I have somewhat of an idea of how to wire it and set it up, thanks to its datasheet, and a veeeerrrryyyy helpful person named Nathan Alspaugh, @NotARoomba
11/16/2025 - DISPLAYS!!!!
So like remember how I had sort of decided the display stuff and stuff? Well now for the decision . . . !
Display Decision!
So yeah here's my plan. I am going to use 2 displays, one LCD, and one E-INK
E-INK:

The E-INK display costs US$19
LCD:

And the LCD is for about US$17
My explanation
This is basically just so that on the most sunny days we (I) have the option to basically just rely on the (veeeery) slow updating E-ink, and on laterally every other day I can rely on the LCD. I was also considering having a heads-up display, basically attached to my helmet, so that I can also just like easily see how fast I'm going, even on a sunny day. This will add more cost thought...
Lemme just quickly draw you a drawing:
======================= . . . . . . . . . . . . . . . . ====================================
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|--------- E-INK ---------| . . . . . . . . . . . . . . . . |------------------- LCD -----------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
|-------------------------| . . . . . . . . . . . . . . . . |-----------------------------------------|
--------------------------- . . . . . . . . . . . . . . . . -------------------------------------------
bro that drawing alone took me 10 minutes because of markdown :(((
Anyways
basically I was thinking of maybe having some keys in the center or around it, along with buttons on the side. I am also very happy about the fact that the LCD has a touch panel, but in the winters it will be sort of useless. Don't worry, it doesn't get cold enough here for the LCD to stop working, though the E-INK might not enjoy being that cold... we'll see...
11/22/2025 2 PM - CELLULAR!!!!!
So yeah, um, as the name suggest I worked on cellular stuff!
So yep as you would think it really is a pain.
So like after reading the first 50 or so pages of the datasheet, I came to the point at which I decided it was time to work on the antenna for the cellular module. As of now, I have chosen to use the MOLEX 2047740001, a cellular SMD antenna, and like yeah it took me like forever to see if they were matchable.

And like that's where the problem comes in. It is alright, but doesn't cover the same bands as the module. The cellular module that I am using can handle the significantly more popular band at 700 MHz, however this antenna is unable to do so, so I will have to see if I want to just rely on the higher frequency bands, like the AWS band at 1900 MHz to 2100 MHz. This will, however, result in lower reception indoors, making it much harder than it has to be to test and debug.
Cellular-Indoor-Penetration-v2-OL
And like y'know the biggest thing I am worried about is if this thing doesn't work. I REALLY want this thing to work, and the thing is I am willing to spend the extra amount of time to do extra research. There is a reason that I spent so much time researching the STM32, and I might still chose not to use it as I really want this to work and I have experience with the esp32.

Well yeah that's about it. I also did spend some time looking into different antenna types, and I really did want to try to use a SMD or patch antenna.
I had tried looking for a guide to help me design a trace antenna, a antenna that is part of the PCB itself, but I was quite unsuccessful, and I really didn't want to make it the reason that my board failed. Like y'know if I had chose to follow a half-good guide, and it turned out that I messed up on the antenna even the slightest, then bye-bye $200 (more likely $250-300)
So yeah I then Googled SMD cellular antennas, and, believe it or not, I actually found something! This was like the first thing, so yeah, but I think I will take a look at other antennas too, as this has a low-frequency problem.
11/22/2025 4 PM - More Stupidety.
So like remember how I was complaining about the previous antenna not being in spec and all of that stuff? Well I decided to find a new antenna.
The new Antenna
So of course, spending a bunch more time, I found a new antenna. It looked pretty solid, was $7.39 on Mouser, only a dollar more than my other choice, and best of all had spectacular low-frequency reception, with around 55% efficiency, and at the AWS band it had a really outstanding 75% efficiency.

Now like if you haven't detected the foreshadowing yet, it's alright. I get it.
FORESHADOWING
I think now you can easily see it.
I went down the rabbit hole of seeing if it was good and stuff, and finding out how to drive it, and then just as I was about to make it my final choice,
BANG!!
I thought of checking JLCPCB if they actually had it in stock. And of course, they didn't.

WHYYYYY
I had even calculated the impedance matching and all of that stuff :heavy-sob:

Of course I only chose to check after getting to the
th page....
Time to go see if there is another antenna i can use...
11/23/2025 - Antenna and STM32
Ok well yepee good news this time
GOOD NEWS FINAAALLLLYYY!!!!!!
In case you can't read, lemme say this again. WE (i) FINALLY HAVE SOME GOOD NEWS!!!!! (and its not even a little)
Thing 1:
I found a bunch of other antennas.
Yep. I literally just Google MOLEX antennas, and i just found 5 or so antennas that JLCPCB also just happens to have, so that's good.


I am thinking of using a low frequency antenna on the board and also a higher frequency antenna, but I am sort of scared of cancelling of signals or some other thing that could do more harm than good. I guess I'll see. I found this good link to combining antennas but again I didn't have too much time to read about it after learning about the STM32
Thing 2: (the last one)
I also sort of got somewhere with the STM32 crystal and stuff, and I am thinking I might start to place some components soon, but first I am thinking of how to set up the PCB, like I don't know what sizes and stuff will look good, so I am thinking of Fusion360ing a protype, a simple thing with proper dimensions for the screen so that I can determine the PCB's size, which will then give me the dimensions I have to work with.

11/25/2025 - More Screeny Stuff
Yeah I sort of decided I was going to model the screens out and determine how I was going to set up the pcb. So I sort of have a few options, I can have a setup like this:

With 3 displays: 2x LCD + 1x E-INK, or
With 2 displays: 1x LCD + 1x E-INK.

3 Displays
So in the option with 3 displays, it has the pros of having a far more beautiful setup, as shown in the upper setup, and I will also have access to 1.6x more screen space, and double the colored screen space.
It would look something like this:

but like y'know, actually beautiful.
it would basically have the highlighted sections have digital dials, with numbers in the center,

And the center display would just have the battery and all the things that don't change very frequently, along with an analog clock so that I can sort of know the time, even if it only updates once every three minutes.
We could have the right display have the speed dial, but now that I think of it I don't really know what I would put on the left one.
I am thinking that I could have the speed dial maybe in the center, with the E-INK off to one side for all the static information, and I could have on the left side the other stuff that updates semi-frequently, like the distance travelled, the cellular status and all that stuff, other semi-useful GPS data, temperature, and all that other good stuff.
One last pro is that I would have more space to work with underneath the board, to try and get it as close to what the cellular system wants.
Cons
However, the main cons to this setup are the fact that the extra display costs an extra $20, and also that this larger PCB will cost more. (About $10)
All in all, this option will cost $30 more.
2 Displays
In the version with just two displays, I would have to cut back on the font sizes, and that would really reduce the readability of the entire system, and it would look a bit lopsided as it would have an unbalanced screen setup, but it would save the other $30
Conclusion.
As my STM32 chip has enough separate SPI lignes to handle the displays on individual ones, I definitely feel like the 3x display option is better.
And, as I (likely will) have 2x CPUs, I can make it so that the STM32 can control maybe the E-INK and the SD card reader/writer, while the ESP32, the one I am more confident with, will be able to drive the other 2x OLEDs.
EXTRA!!
And, as a bonus, I have this cool little surprise for you!
I finally have a rough cost estimate. We are looking at around US$130~150 for the PCBA, including the 6 layer PCB, while the extra displays will add about $80. The SIM card and data plan will be about $15, and all in all, add misc. stuff, and you get about a total of around $250~260 USD.
12/6/2025 - Started Making the Schematic and Routing! & COST ESTIMATE
Halo World!!!
Yes. It is me. I AM BACK!!!!!
Enough pleasantries.
Ok, first up,
ANTENNA!
So like a bunch of time and research later, I have chosen an Antenna! I am going with the 1462000001, a 698 MHZ - 2.7GHZ antenna!
Basically, I just datasheet dived. Datasheet dived a lot, and like it seems pretty good! Gรฉnial! And like y'know if that isn't good enough, it is most efficient at the target frequencies, the most carried frequencies around me.
And, then, I found out about antenna matching.
Antenna Matching.
Like if someone just thought. "Hmmm. I think that being an RF engineer is a really easy job. Why don't I just through ANOTHER wrench into the process.
So like they just went and messed up with the antenna's frequency efficiency chart, and just did this:

This is basically just a graph of where the antenna is most efficient, and like the yellow line is with matching, and the blue without. Lower is more efficient. As you can see, the blue line may look just fine, it is just efficient at a different frequency.
BUT
the problem just is that that doesn't do much. You need the antenna to be maximally efficient at the frequency you're going to be using it at. And guess what? The majority of people will be using it at points where the yellow line if lower. So like y'know, why can't they just make the yellow line the default!?!?!?!?
My Circuit.
So like we talked quite a bit about that stuff, now let me show you my current schematic and layout.
Firstly,
We have this thingy:

That's basically just the antenna, with its ground, and the signal line. Then, below it, we have the antenna matching circuitry.

This is basically just what the datasheet recommended for a 130mm x 60mm board. And guess what board size I have? 130mm x 60mm. Yeah I'm not about to mess up with this stuff that is adding $50 to the cost!!!!
Current cost.
Speaking of which, here's what my current cost breakdown of just the PCB(A) is looking like:

I know bro. I'm already at $100, after using the $20 coupon for a 6 layer PCB, and after a cheaper form of shipping which will inevitably be more expensive for the higher-value finalized PCB.
And all of this is not including the $70~80 for the LCDs, and ~$30 for the SIM stuff, likely lower.
All in all, I'm already at $200, and really just need to make the rest of the PCB as cheap as physically possible.
btw, me asking for confirmation that my stuff was good to ~3hrs in and of itself

12/13/2025 - Block Diagram!!
NOTE. I use OLED / LCD interchangeably, even though I mean this LCD
So yep as the name suggests, I am working on a block diagram to make my life easier. HERE is the block diagram's latest version, but as of now it is just this much:

It also got me thinking of how I was going to set everything up, especially in terms of power management.
Power Management
As it is right now, the esp32 consumes the following amounts of power:
| Mode | Power Consumption |
|---|---|
| Wifi Transmit | 180 000 ~ 240 000 ยตA |
| Active Mode | 90 000 ~ 130 000 ยตA |
| Modem-sleep Mode | 20 000 ~ 70 000 ยตA |
| Light-sleep Mode | 700 ~ 900 ยตA |
| Deep-sleep Mode | 10 ~ 150 ยตA |
| Hibernation Mode | 4 ~ 6 ยตA |
For reference, I am going to use 2x Molicel INR-18650-P28A.
This is a 2800MAH 18650 battery, and with two in parallel, I get 5.6 AH.
If the CPU alone is using that much power, I might be cooked. The screens will likely draw about 100 ~ 150 MAH. Alongside that you have the cellular module, GPS, IMU, and a whole other CPU, the STM32U575VGT6.
But the good news is that the STM32 uses just 19.5 ยตA/MHz, and if I just run it at around 16 MHz (which is about the clock speed of the Arduino uno) it will draw just 320 ยตA
Even at full speed, it will just draw 3200 ยตA or 3.2 mA
And if you were wondering how that is possible, very simply it was on purpose. I chose the most lowest power thingy magig I could find, and yippee! here we are.
So like the problem is, which devices should go with which CPU.
The ESP32 is less power efficient, however it is more powerful, has bluetooth, and a tonne of RAM.
And ofcourse, the STM32 has power efficiency on its side.
So here's my block diagram again:

Basically, my plan is to run the ESP32 when I am biking, and along with it the GPS, GSM, and the OLEDs. This way, all the higher power stuff will only turn on for shorter periods of time. Also, just because it makes more sense, I am also going to put the wheel speed & distance sensor connection to the ESP32.
On the STM32, I am going to have the lower power equipment thus the E-INK and motion sensors, but also I am going to have it be the main CPU that will always be on. This way, it will be able to control when to turn on and off the ESP32, and as the have a UART connection between them, the ESP32 will be able to signal its choice, perhaps after it is done with periodically checking if the bike has been stolen.
P.S. I accidentally added an hour last time, so I am counting one less this time.
12/14/2025 - Routed, Re-Routed, and Re-Re-Routed the ESD Protection
Ok. I get it, that was a long title. And also I am sorry, I don't have as many pictures as I would have liked to have. Anyways, here it is, my current version of the ESD Protection.



My Reddit Post btw (it has some pictures from my last rerouting)
Explanation Time.
So basically how it works is that it just has some 22 ohm resistors near the module, really really close, and those are there to filter out noise, just like how it was recommended in the example circuitry.

Here, we're talking about R1, R2, and R3.

My design ^
Example Design v

ESD & TVS
Then, we also have a TVS. This can be seen in the reference design, just above, and also here are some close up shots of my work.
Here is the TVS protecting not only from ESD, but also capping the voltage first, by putting it into vdd.

And routed:

Curvy Routing
In case you didn't notice, I am trying to use curved routing for this stuff, as it basically helps with making the signals clearer, even better than just 45 degree traces.
As a bonus, here's my routing so far:

Note
Again, you might be wondering how I managed to spend so much time routing, but it just is a slow task for me, especially with the curvy stuff, and also I first spent a bunch of time trying to route it all on one layer, and in affect, I have that. I just moved the data line to another layer, ground in between, so that it could be protected and not have stuff induced into it. I only have a via for ground, and one for power, VDD.
12/19/2025 - e.g. Routed the PCB
Always wanted to do that
Anyways, I got this level shifter part done:

Just don't mind the left side which still needs work.

But like anyways, this one was quite tricky because I wanted to keep those wired nice and tidy, and avoid vias to 1. disturb the ground plane, and 2. block the other 5 layers. As you can see, the left side still has to be done and I really do have to find a way of doing that...
BATTERIES!!

I also set it up to have the 18650 holders, as they consume the most space and block a lot of stuff, but also because they have to be relatively close to the GSM (cellular) module because they need some FATTT traces.

STM32
This to is going pretty swimmingly, as I have started on its schematic.

Just one hiccup, this STM32 requires a beefy capacitor at the VCAP port, but it is also required to have really low ESR at 3MHz, which is actually very hard to get. Thus, we have the option to trade the 4.7uF capacitor for 2x 2.2uF, as these are waaaaay easier to get in appropriate sizes, and the ESR requirement per capacitor halves or doubles or something just making it a whole lot more lenient.
Side note
I am a little concerned about how this all is going to be, as the space is there, just a little tight, but most importantly I still have to get the displays and stuff going, main power trees, and also design the case in <10 days to submit for Hack Club's Blueprint.
12/21/2025 - e.g. Routed the PCB
Did it again.
More Schematics and Routing.
GSM (Cell)
I think I might have just finished the entire schematic & wiring for the GSM. I'll double check it later, but real quick:
1 - I wired up the status LEDs

2 - I wired up some HUGE caps


3 - Wired up a header so that no matter what I have the cell stuff to test for next time I do this

4 - Wired up the connection between the voltage shifter and the ESP32

5 - Set PWRKEY, the main power controller of the SIM7080G, up.

ESP32
As for the ESP32, it was just mainly me running a program on one of my existing boards with the same chip, and I found that only GPIOs 22 - 38 don't work (38 does, 22 doesn't)
I also wired up the USB line/port

LCD
Found some FPC connectors


and these are going to be the connectors that connect to the LCDs, as the LCD comes with a 18 pin FFC (like a FPC) and the LCD has a connector. This just makes the physical wiring job a bunch easier, as I now longer have to solder some 26 thin wires that keep breaking in my face. (Speaking from experience. Looks down. :SOB:)
STM32
Well I have a bunch of capacitors...

Need to wire that stuff up.
In other news, I sort of got the SD card stuff ready, and I prepped the USB port for wiring

P.S.
I really am glad I chose a 6-layer PCB, as I am already using 3 of the wiring layers, and it is essential to have huge solid ground layers for the antenna.

There's a reason why my board is shaped like that, and why everything is so clumped up. Maybe I should spread stuff up, but I sort of have to rush ... only ~8 days left to submit.
12/22/2025 12 PM - Some Display Planning.
Alright. Here's the plan:

Basically, the big green rectangles are the displays, LCDs on the edges, E-INK in the center. The other green rectangles are the 18650 battery that are going to be used. With this layout, we have the antenna kept as far as reasonably possible, but we still have a huge-ish area,

As you can see, it'll take up around 168 MM, in width, and about 110 mm in height. To make it symmetrical, it's probably going to go to ~180MM, which isn't crazy, but still.
12/22/2025 5 PM - More Routing & Schematics
UPDATE!!
Here's my most recent pictures:



STM32
After a bunch of contemplation, I have decided to but the STM32 in this little corner all by itself:

But another problem, USB lines:

y'know how it needs 90 ohm differential routing and all that good stuff? well yeah that's why those wires look so weird.
Vias?
Another big no-no I have because of how the two signals need to cross over each-other. WHYYY?!?!?!?
Crystal
One good thing though is that my crystal wiring and stuff does look pretty good... 
GPS
I also wired up the entire GPS, did all the comparing between different models and stuff, and that.

You can see it with its keep-out and stuff. It's the L80RE-M37, and I still have to do the RX/TX stuff, but that's a quick 5-minute job.
...
And yep I just finished that too.
Display connectors.

Although not wired, I have found them a place
Challenges
Coms
My biggest problem right now is that I need 3x UARTs on my esp32, but although it technically has 3, I'd rather not use UART0, as it might bug-out.
So, instead of uart to talk between the STM32 and the ESP32, I could use SPI, but the thing is there is not a thing on the internet, after 1+ hours of searching, for SPI slaves in Micropython.
12/23/2025 - More Routing & Schematics. Again.
Sorry for the rushed journaling Yesterday and Today.
Current Stuff


Power (esp32)
So yeah, I wired some POWERRRRR

Basically that entire setup took me like 3hrs to do, because that datasheet was just not being processed by my brain. It was almost like trying to get meaning out of a blank piece of paper. And then, after I finally got stuff, I had to find the proper inductor, placement, and all that good stuff. Y'know the stuff that takes you hours...

Power (Main)
So yeah, I wired some POWERRRRR
Here's the INA226 circuit, the device that will measure the battery voltage and current draw

This was the most straightforward thing I've done as I've wired this one up like 5 times before, but I still had to find the shunt resistor (10 milli ohm) AND the good wiring stuff, like Kelvin traces and stuff. All over hyped if you ask me...

Power (GNSS)
So yeah, I wired some POWERRRRR
oh i've got to stop that...
Anyways, I wired up this MOSFET:

And it will allow me to control when the GSM module is on and that good stuff, but yeah that also took like forever because I had to choose the right king of MOSFET (P-Channel was a whole lot easier) and I had to then find a suitable MOSFET, and wire it up... Takes time, I tell you.


I also added the simply boot and reset buttons for the ESP32, and its looking quite good. I just have to get some final connections between the Cell chip and the ESP32, along with wiring up the majority of the STM32, including the toggle to control the ESP32's power line. (btw it draws less then <1uA when it's EN pin is low!)
Sorry again for the rush, I've been grinding too hard clearly...
12/24/2025 - More Routification
Again, I'm sorry its getting very late. I hard grinded...
Current Pics
Anyways, lets start with some cool pictures of my current setup. You'll notice a hole lot more stuff.


Stuff I did
Basically, I just worked on some ESP32 stuff, and the foreign language of the STM32
ESP32

Simple enough, just wired up the UART connection, and the SPI connection, both to the STM32. They are just some data communication lines.
STM32.
This was the big boy today, with a holy lot of new stuff. Before, there was just the crystal and some decoupling caps. Now:

You can see that there is the COM lines, and also a thing called an SD card. Yep, I wired that up, and that took a lot of time because apparently SD Card lines are sensitive. (Oh yeah also I had to manage vias, almost went on to the fourth (and final) signal layer, and that stuff.
SD card

Here's the SD card slot, and yeah. Last time my setup didn't work, but I hope that with the pull-ups and the 33 ohm series resistor it will be ok, even if it has to be slowed down.
The SD card did take by far the most amount of time as it has to be wired so sensitively and has 6 wires, all so special...

Final note.
I have left out the boring and repetitive stuff as I really need to get to bed and stuff, but yeah I think you should get the gist of it.
I am also going to work on the main display SPI as it is important and high-speed... (tomorow)
12/25/2025 8:27 PM - Main Display SPI & STM32 Power
Yep that's all I did. Byeeee...



Still here? Well in that case, Let me elaborate, for you my highness
Displays
It was a good idea that I started on the displays.


The fattest of these ribbons are actually there just because of the display. The green was is for the 5-point touch on both displays, and the red wires are for the (hopefully) 80 MHz SPI bus.
To run this ribbon, I had to rewire so many things to make space.


Like the slower SPI bus here. For the microSD card line, since it was also really high speed, it couldn't have any vias.

That's why this area had to be rewired to move the Power-Generation circuit over.

Even then, the slower parts had to jump under the main, high-speed, signals.

I know this doesn't seem like much, but when half the board is already done, wiring even a little more can require changing a lot of stuff.
STM32 Power.

I put it this Buck-Boost converter here. This converter, somehow, has a Iq of just 75 nA! That means that only 75nA will be used when no power is being drawn by the STM32. (That was the biggest reason I chose this setup and buck-boost, even if it was a bit more expensive.)
I also added a MOSFET to control the supply of current to the low-power electronics, like the INA226 and the (tbd) IMU

12/25/2025 8:38 PM - Double checked costs.
Just a quick check.
Here's the cost of the PCB:

And including everything:

I am just really worried about clearing customs, and also paying the local GST.
12/26/2025 - Just a bunch of Finishing Touches.
I think I am going to try a new type of journaling.
Basically, I am just going to tell you what I am doing, add pictures as I work, instead of doing it afterwards.
Re-Check
I was just checking over some schematics, and voila! mistakes!
Basically I just completely forgot to add the VDDA wiring, basically the power line for the analog sense circuitry.

I have now added the 4 capacitors it wants.
I also wired up the "Power Good" pin, which basically just monitors the ESP32's power supply to see if it is overloaded (should never happen) or is giving messy power. I could temporarily turn off the backlights or something to let the power line re-stabilize.
LEDs
As the stuff is coming together, I worked a bit on LEDs, I added one on the ESP32 side, for power, and on on the STM32 side, this being just for indication and debugging. The biggest problem with adding additional stuff right now is that it makes everything much, much harder. Like to add the STM32 LED, I had to try so many different places, and eventually settled on this one:

When I added the "Power-Good" wiring, and the "Power-Enable" wiring (to control the ESP32 regulator) I had to put in many more vias, etc, because as the wires went, they broke through the invisible ground planes, on top of everything else...
Ferrite Beads
After a bunch of help from @tty7, I decided to add Ferrite beads to the USB ports to reduce their antenna-like properties. (L6 btw)


IMU
I also got help with finding an IMU to detect when the board is shaken, maybe to remove it during a theft or if the entire bike is being stolen, and it's the ICM-42670-P. (Still have to wire it up tho...)

Note:
Tomorrow i might, after finishing the IMU and Compass, go do a quick run of the 3D model. Due date is approaching quickly...
12/27/2025 - Finished Designing and Routing the PCB
I get it ... the title is too long.
Pics!
So yep here is my PCB, all done!

It looks way more impressive when you take a look at this picture:

Note: I am still going to add the stitching vias at some point.
IMU
I wired it up, checked datasheets, got Gemini to check it, and got this nice guy, @tty7, to help me check it out.

Compass
For future in-built navigation, and just a cool feature to have. I had to go look through a bunch of options on JLCPCB, and this is the first one that came up that was supported by economic PCBA, and also was compatible with the STM32. It took me a while to see that it was actually compatible, as it said 0.7V, and then real small VDDIO. Basically that means that it drives high at 0.7 x VDDIO, not 0.7V. (STM32 needs ~2.3V)

Another cool thing is that it uses BGA, but because of that I had to use a tiny 0.08 mm trace, and cut-down the Design-Rule clearance, hoping nothing bad happens... (The two decoupling caps are bigger than the entire chip!!
Headers
I added two (2) headers, one for the hall effect sensor that will count the amount of times the wheel rotates, and one to hook up the E-INK display.

I know this is rushed, pls forgive me.
12/28/2025 8:09 PM - Double Checking ... A lot of double checking
Yep just as the title says, I did a bunch of double checking. The two biggest things I found were that
- The compass wasn't connected to power, and
- The main Cellular power switch wasn't hooked up right.
In more detail:
Compass
So if you look closely at my last shot of it, you'll see its hooked up to the LP_3.3V line. However, for whatever reason, it wasn't saved when I came back to it today, and well, yeah that would be bad. So, after adding the power lines, here it is.

Main Cell Switch
So remember this part of my schematic?

Yeah, as you can see, the high-side MOSFET will switch the power on and off, as long as you provide it a signal. See that? You need a signal. Well yep clearly, I thought there would just be some magic and it would just work.
So well yeah I just added a connection, to pin 6, (IO6) of the ESP32
Power Saving
So you have to think, what about power saving. What if I don't want to change my batteries out every week? Well then in that case you have Power Saving!
So yeah instead of having a main MOSFET, with leakage currents at ~15uA, a lot in this case (total low-power power consumption is ~252 uA) I am going to have the power be delivered from the GPIO of the STM32.

like this ^
One last thing, to have the STM32 in ultra sleep mode, I am going to have the set up be that the Hall-effect sensor is (for bike speed measurement) is going to be on a wake pin, so rolling the bike should wake the system up from its ~25 uA sleep setup. (BTW at this consumption it should run for a few decades!)
GPIOs

So yeah as you can see in that image, I am sort of out of GPIOs on my ESP32S3, however my STM32u575 still has 36 / 82 IOs open.
I also added a screw terminal so that I can just screw in the wires that connect to the wheel's magnetic sensor.
Feedback
Not only did I upload my entire schematic to Gemini (only useful for finding the BMM150 (compass)'s lack of power), but I also uploaded it and asked my friends who have used the STM32 also, one of whom is using almost the same chip. (STM32U575 vs STM32U585)
The biggest thing was the power consumption that @tty7 helped with, and @NotARoomba helped a bunch with helping me get started with the STM32 and looking over my schematics.
Note
I really have to get going with my 3D modeling, but I've heard that Blueprint will be extended to Jan 31st...
12/28/2025 8:13 PM - Just some pictures
I didn't have any good pictures last time so I am going to add them now.




I also cleaned up the layout of the huge 100uF caps and made them closer to it


12/28/2025 8:49 PM - Replaced Shunt
Yep, I just simply did some calculations, and my current shunt was way to small, just 10mฮฉ, and I replaced it with a 20mฮฉ one. That means the maximum power went from <8A down to <4A, and that was enough to sort of concern me, especially with capacitor inrush and stuff, so I just checked around and stuff and I eventually was just like bruh whatever. The thing is, 4A is sort of close, as the SIM7080G can peak at 2A, and the displays could probably hit 1A, but I don't think the rest of the stuff will be able to hit another 1A.


BTW the shunt has the same package, so all I had to do was find a good one.
The shunt is +- 1%, so the my accuracy is only +- 0.04A at full power, but at low power (<1mA) its the INA226's 16-bit ADC that's the limitation. Thus, I changed the shunt to a higher resistance and higher voltage drop one so it can make out the difference easier. (It effectively adds another bit)
Accuracies
first number, current consumption.
second number, accuracy (either the ADC doesn't have enough bits/resolution, or the shunt isn't accurate).
@4A -> 40 mA (1%)
@1A -> 10 mA (1%)
@100mA -> 1mA (1%)
@10mA -> 100uA (1%)
@5mA -> 61uA (1.2%)
@1mA -> 61uA (6.1%)
@250uA -> 61uA (25%) (low-power operational current)
@100uA -> 61uA (61%)
Notice that creep? +- 25% is a lot at operational current
12/29/2025 8 PM - Suture Vias (Stitching Vias)
I worked on the stitching vias that basically connect the layers together. It was a bunch of checking what sizes are alright. At first I tried guess and checking. I started off with 0.8mm via spacing, and for some reason everything was glitching out like crazy, but eventually they applied.

Then, I tried 0.7mm, 0.65mm and then 0.6mm. They all worked โ however it sort of felt weird. Thus, I went googling what JLCPCB can produce and those clearances, and it hard core hallucinated. It said some crazy numbers, but it also gave JLCPCB's specification website.

From there, I had to go into the drill section, where it said that the via holes should be about 0.2mm apart:

But that feels like its way to close, as some existing vias I have are 0.305mm apart:

And EasyEDA doesn't allow vias to be any closer. But, sticking to what JLCPCB said, I changed the Design Rule โ and thus the DRC (Design-Rule-Check) โ to lower the spacing a bit, and MESSED EVERYTHING UP...
Yeah that's something I'd rather not touch again, but it still sort of works, and I just didn't bother changing the hole-to-hole spacing (needed to be >0.3mm to >0.21mm) and well yeah just figured 0.6mm spacing was alright.
So yeah, all in all my vias are about 0.6mm spaced, however near the PI network near the antenna, I have a denser grid of a basically hexigonal layout:

12/29/2025 9 PM - 3D Modeling

Onto 3D modeling, I finally got started on it.
Basically, as it stands right now, I got everything loaded up into Fusion, and sort of got back into the hang of things.
LCD
I found an official 3D model of the LCD on waveshare's website, and here are some pictures. (It did take some digging, as I originally searched on other sites, like GrabCAD and Thingiverse. It was just random luck that I happened to go to the wiki, and that I just happened to find the model.)



PCB
I just quickly imported the 3D file from EasyEDA, and then realized I hadn't added any mounting holes, so I added those in the weirdest spots.
One is dead center-top,

And the others are on the very bottom, as you would expect.

Here's the model in Fusion

E-INK
I was sadly unable to find a 3D model of the E-INK, not from waveshare, nor online, but I have sent them an email.
Instead I found this case thingy-magig, but it's not any good :(

It's actually quite weird.
Anyways, I just made my own sketch of the suggested outlines, and well, there it is. Its not eventful or anything, but still:

All Together
Here is what it looks like as of now, with the PCB going to be on the top, then several ground planes are going to protect the GNSS antenna from the noise of the LCDs, and the screens on the underside.

I am working on a truss to hold the screens in place, and it's going to be like the blue thing and slot into the main thing. I am thinking I might get this machined so that it is ultra strong, as necessary for a theft-proof device. However for testing, just a 3D print should be alright, and then I can see if there is a future hack-club thingy in which I can design a unbreakable case. TBD.
Well yeah that sums about all that I have done in terms of the 3D models.
12/30/2025 - 3D Modeling, but slowly enough you'd think I'm a sloth.
Yep, I did 3D modeling.
Yep, I was slow.
Yep, you might wanna call me a sloth.
My model

As you can see, I have some stuff done, and the biggest part is that I have the main structure that carries the screen.

As you can see, it is a little complicated, but obviously doesn't have fancy fillets or anything. Firstly we have screw holes to mount the screens to it, and we also have the main structure to support everything. It has a hole in the center to allow for the batteries to poke through.

Of course it'll be more polished, but that's how the rough outline goes.
E-INK
So yeah I got an email back from waveshare, and they said they didn't have any more accurate drawing. Well that sucks, but I found a 3D model that was a case for one of those displays, at https://www.thingiverse.com/thing:4639641 In the example pictures, they showed it looking like it was almost perfectly a container, but I think I am going to have to put on the final touches at the end, once I get the E-INK and can actually measure it.

Color & Other
Color
In other news I figured out how to add color to the objects I model, and I also spent a while trying to figure out the optimal setup.

Assembly
As of now, I have it setup that the PCB is on top, then the holder, and then the batteries and board on top of that assembly. Next up I am going to design something to go under, for the plate I designed, and then something to go on top.
As my plan is, I am going to have 3 pieces, that will probably be screwed in place or something.
Deletion
So yeah somehow a bunch of my work got deleted midway, and then when I tried undoing/redoing absolutely nothing happened, but it wasn't a huge amount of work, so not a huge deal.
1/3/2026 3 PM - A little bit of power consumption metrics
I was just wondering about how much power the SIM7080G uses in its different modes, and a quick search gives the following: 
(Actually it was not quick because of how dog-water google has become)
Basically, when its idling it pulls 10mA, basically nothing for my on-mode, and when it's ready to shut up and sleep it draws just 3.2 uA, less than the GPS's backup mode (for its RTC) and way less than the MOSFET's leakage current of 15uA.
umm i also just forgot, when i came to do another journal i just saw that i was midway through my last journal entry so like umm...
1/3/2026 4 PM - Minor Power Consumption
So y'know how I said last time that my MOSFET's leakage current was like 15uA, and the module's PSM (Power Save Mode) draws just 3.2uA? Well yeah so I just decided to replace it with a switch, just in case I ever want to turn it off. (I don't know why I would, but still)


Side note: I still have trouble believing this small red switch is rated for 5A...

This way my total current draw (in max sleep mode) is now at:
| Device | Power Consumption |
|---|---|
| GPS | 7uA |
| Cell | 3.2uA |
| STM32 | 640nA |
| ESP32 Bypass1 | 100nA |
| Quiescent current | 75nA |
btw this took a bunch of research for new, updated numbers.
As you can see, the biggest power consumption comes from the GPS, but it would have been the MOSFET. Whereas the current current consumption is 11.015uA, the previous one would have been about 22.8uA, which is significantly higher, and had added points of failure.
With these numbers, we get a standby life of about 21183.235 days, or about 58 years on a full charge. Which is of course, honestly, CRAZY.
1/9/2026 - Screw
After a (relatively) long break, I am back at it again!
My goal right now is to speedrun to protective shell around the PCB to prevent any harm. As there is sensitive RF on the board, unfortunately I will have to 3D print it, and CNC is out of question.
Ok. Its sort of time to get serious, as I am nearing the submission point of my stuff.
Screws
First up, it is really important to find screws that will, you know, actually work.
So first up, I took a look at the official datasheet for the LCD screens.

Basically, all it really shows is that the PCB to top of the screw hole is 4mm,

and the screw size is an M2.5.

I found some M2.5x8mm, M2.5x10mm and M2.5x6mm screws.
the thing is, though, that I also need a screw bit to drive the screws, as they have something like a T8 head or something, so that's that.
And even better, according to the Waveshare Docs, the E-INK also uses the same size screws too!
However, as I don't have the 3D model or anything, designing a proper size case to protect the screens in the event of theft/crash I might have to get the screens first...
All in all it took about 40 minutes to look for this information and just start the 3D modeling.
Main Truss.
Ideally, the main truss will be built from CNC aluminum, and that that you see below (in red) is what it looks like.


This side will be for the Screens:

And this side for the main board.

It is significantly higher up because of the huge batteries that will be in between the board and the screen, and thus it acts like a spacer. I am trusting that the aluminum is strong enough.

One (not at all) small thing is that I have to deal with the cost.

$40+ for the part, plus HST, plus shipping will add to quite a bit. So instead I am going to make the PCB mount to the top cover, which will mount to the main truss and the bottom cover. Only the PCB cover (the top cover) will be 3D printed for RF, but the others are all CNC.
Apparently, the fabs don't like all these weird extrusions. Who would have thought...
So I guess that all the work I did today was for absolutely nothing (not really the screws and stuff were useful but still). How terrific. I guess.
Well mounting hardware deleted!


Hey at least its colourful now!
By the way, thanks to @Raygen Rupe for helping me in getting the main mounting stuff lowered in cost and stuff. But yeah a big bummer that I have to redo this :hs:
1/10/2026 12:12 PM - SCREWSS!!
Do you love screws? I sure don't
Like bro I would have thought someone would have designed a better way of doing all of this. I just had to resize all my holes, and then on top of that because the screws are tapered, I had to taper the holes.

That might not seem like a big ordeal, but the thing is for a noob, you have to account for all the extra work of figuring out how to make the tapered holes, and understanding whatever the heck this means:

Oh yeah also I forgot that you know, fusion is like the weirdest software, completely counterintuitive, so that doesn't help. (Not as bad as Adobe though)
Oh, and here's some pictures:



1/10/2026 12:18 PM - Fileting!
This is another task that should be the easiest in the world, but Fusion is being a donkey.
See, the outside fillets were easy-peasy.

But these donkeys thought it was a good idea to keep every small face you make. So, on the inside, it was a hole other story. (get it?)
At first these lines on this face don't look like a big deal, but drum-role-please ... they are!

When you zoom in, to fusions max, you get this:

Somehow, those two plates are separated by 0.002~3 mm, and I was unable to actually fix that, so I guess there's not going to be any filleting there :(( (fusion only allows me to move the sides by 0.001 mm at a time, and seeing as the difference is like 0.0024, I can't do anything about it. What a waste of time :(
1/10/2026 1 PM - Top Bracket.
So um yeah that's about what I worked on.
Firstly, I had to think about the dimensions I was going to set up, and here's the sketch I settled on:



Basically, it looks like a big block, except for the fact that there is a recession in the center for the PCB.

However, I then noticed that it looks quite beefy, too beefy, and saw that the biggest part, by a very significant amount, was the screw terminal:

Thus, I went back to my PCB to change the mount, but then, ofcourse, it took me like forever to find any decent part, so yippee. :(
While I was re-rendering the 3d model, I noticed a switch didn't have a model, so I went looking for a new switch, which again took so long that I realized it wasn't even important :(
Now, all I have to do is change out the PCB model for the new one, and I'll shave some ~5~10 mm.
Re-aligning the new PCB was a real easy job, and I have an updated board now! only real difference is that there is now a switch to control GPS power, and a significantly smaller header.
btw, the header is used for connecting the wheel-rotation sensor to the ESP & STM, and can act like a ultra-slow-speed back-up comms line.
5mm thickness saved!


Just one note, I am thinking that I will move the E-INK header inwards, so that I will be able to have a significantly compactor case.

1/10/2026 2 PM - Moved Header In by 6mm
I rewired the entire header part, and I also moved it in by 6mm, just like that that the title suggests:

It also actually simplified wiring, because now the wires could go from the right side, and thus I have to use one less vias.
This also allows, as I said earlier, for me to condense the entire thing!

(I'm sorry you're gonna have to compare it with one of the earlier images, as I really can't get fusion's timeline thingy to work with me.)
1/10/2026 6:21 PM - Standoffs, etc.
I 3D modelled in some mounting points for the PCB, so that y'know it can be mounted. I am going to add some holes, and well yeah.


Basically I just sketched some holes, and made the stand offs 8mm wide. I am now going to add the holes that will allow the not-self-tapping screws to self-tap themselves. (btw I've done this before it actually works).
Ooh, yeah I also made the back side look absolutely spectacular, including some really big fillets!


Once I add the mounts, all I have to do is add the bottom cover and find a way to connect everything!

1/10/2026 6:58 PM - Added Screw Holes to the Top Mount
Yep. Here they are!

(just in silver instead of black to make them easier to see.)
Because they are designed for M2.5 screws, how it works is that the very top is 2.6mm, and as the screw is rotated, it ends up being able to tap itself in the plastic. To keep the grip, the hole becomes smaller as it goes, all the way to 2.4mm.


But yeah it took me like forever to confirm that this is ok, and I might have to redo this all anyways...
1/10/2026 8 PM - Stuckification
I'm sort of stuck rn.
Like i don't really know how to build the other part of the enclosure, especially because I don't really know the dimensions.
Let me explain.
So basically, here's what I have so far.
BACK:

FRONT:

The thing is, for strength reasons, it makes most sense to machine the plates, however the machined items should be just that: plates. I want to make this entire thing as cheap as possible, so I am sort of confused.
So here's the thing:
I have a plate.

But I don't know the size of the E-INK. that's ok, i can put that in once I get it.
The problem is here:

I need a way to basically just attach the bottom, the middle plate, and the top plate together.
I am thinking that I could just extend the plastic cover further, however that would end up making the vast majority of this out of a weaker material.
I guess that really is my only good option...
Yeah. After some thinking, I think I can actually make this work.
hmm...
Yeah I think that I can make it so that the plate will attach to the PCB shell as it is, and from further on top it will just be a shell thingy, as its only a bit taller, about a CM. Once the main part ends, then the top plate will be attached to the top.
I still don't like this...
1/11/2026 - Vias, IMU, and Costs, and Impedance Matching
I don't really know how much I talked about Impedance matching before, but that's first up!
Impedance Matching
So like I sort of messed up the previous impedance matching, as it was accidentally set to the signalling on layer 2, not the top one. That was a relatively quick fix, but I had to double-triple check that it was correct, and then double-triple check that the stackup wouldn't mess with anything else.

I also had to re-place a bunch of vias around the line, as making it slightly thicker made it so that the vias were now too close for DRC to like.
Suture Vias
Y'know how before I talked about via spacing, right? Well screw that I'm not going to be putting a via every 0.6mm. Instead, only in the GPS area, I will have some 0.7mm spacing,

and everywhere else I'll settle for just 0.2MM spacing. 
Again, this was with the help of @tty7
IMU
For whatever reason, JLCPCB chose to make the IMU I was using into a standard assembly part,

But lucky enough for me, my friend @tty7 was able to point me to the ICM42 series IMUs, in which by random chance I picked one with very similar functions, and the exact same layout. All I had to change was a single capacitor from 10nf to 100nf.
Cost
I know all of this because I just so happened to have been checking the total cost. All IMU mess was just a byproduct.

Here's the picture before taxes and everything. You should note that the PCB is $42, and assembly is $160.
Assembly.
Out of assembly, you can see that about 42% are one time setup fees, about 40% are the components' fee, and the rest 18% are just for soldering and double checking the solder is good.
note.
It should also be noted that the fee is without any coupon being applied and without shipping and taxes
Lora
Yep. I spent some time contemplating whether or not putting on lora is a good idea, especially as a low-operational-cost alternative to cellular, if it even works.
But I sort of came to the conclusion that it wouldn't be worth the cost, as I would also need a receiver.
Cell RF.
Well yeah that was close. So mainly that that came out of my conversation with @mpk was that I needed to change my trace widths, but not by a ridiculous amount. For the coplanar part, that is.
So basically I had to change this wire to 3x its size, ~0.11mm to 0.35. Well that is absolutely huge, but other than that, I only really had to move the matching circuit and all of its vias in line,
and also put vias all around the board by hand to limit the board doing funny stuff, and I also had to recalculate the coplanar wire parts, several times, because it wasn't matching Max's calculations, and then I had to look up the prepregs' dielectric constant to see if that was the problem, and then I just ended up seeing the difference with my calculations and his, but all in all it was a small 5%.
Yeah by the way that was sarcasm. It was a metric tonne of work. AND i also forgot to do any work for my other duties...
1/21/2026 - 3D modeling
I finished the base stuff!
I just have to add some screws so that the main truss can connect to the other parts, and so that the top plate can do so too.

Finished case:



Some inside shots







It was pretty simple of a thing ngl, but I still had to figure out how to split stuff. Next up, the screws & mounting to my bike. After that I'm going to speedrun some software, and try to submit this!
1/23/2026 10:38 PM - Finished the Case!
Ok firstly, I did this work yesterday, but i grinded too late so i couldn't journal
Screws
screws, screws, screws.

Basically, I just added a bajillion screw holes with counter-sink. took me way too long, but hey.

Bike mount
I went outside and measured the size of my bike bar, and modeled around it!



in all:

1/23/2026 10:47 PM - Photo Realistic Renders.
As anyone with any blender knowledge will tell you, blendering is crazy time intensive. Especially if it's your first time. After about 8 hours of work, here it is:
A photorealistic shot:

But of course, i have more for you!
So in class, we had an animation unit, and I got the basic hang of how animation works, and I implemented that into a setup on blender. Here's the stuff! (I did mess around with the lighting, animation, and background so it looks better. First, some shots:



And here's the video if you can access it. It has no sound btw.
1/24/2026 - 3D animation
I got this fire animation to render! it took over 21 hours! that was absolutely crazy and actually cost me a significant amount of electricity.
Here's the render!
You can also see the PCB now.

1/27/2026 - Finished Everything Up: Made it Submission Ready
Worked On the BOM, got all the prices, simulated checkout.

Also got the Github Repo looking nice. I have the CAD, with the F3Z file and the STEP file. I also have the Production PCB stuff with the gerbers, BOM, pickandplace and the .EPRO for the easyeda project.
I also worked on the Firmware, which took FOREVER as it is like impossible to figure out how the STM32 stuff works. I think I have the basic code in the repo, but idk.
the VAST majority of time went towards the firmware and BOM. Checking everything out take a bunch of time...
1/28/2026 - Some Basic Routing Fixes & STM32
So firstly I spent like a bajillion years on trying to figure out how the STM32 should be set, and just ended up adding a debug header, alongside a pull up on the NRST pin. The debug header made really good use of the mostly empty 4th Layer.

In addition, I also cleaned up the area around here:

So that there are only 3 layers being used, to make it so that the fourth layer is as much ground as possible to make sure the antenna is really, really happy.
2/4/2026 - Compass is in stock!
Previously, the main compass of the board was out of stock, and the pre-order was showing inflated prices, thus I am able to save about $6!!!! Yippeeeee! I'm also thinking of adding the battery guage ic known as the MAX17048
My compass setup:
2/12/2026 9:28 AM - Just a note
Umm yeah for some reason I went through the jlcpcb checkout again, and the cost went up by $10???
I'm really confused rn...
Well um i need characters so!

2/12/2026 9:29 AM - Wrong image previously
chaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrsssssssss.

2/25/2026 - Bought some stuff & checks and stuff
Mostly just double-triple checked stuff and bought it!

So yeah, the thing is that one of the things was from a blocked category, so I had like get @jay to unblock it, but then he had to re block it, but the problem was I had 2 orders, so he had to do that twice...
Well anyways I probably did some other stuff but I forgot it all so...
2/27/2026 6 PM - Rerouted a bunch of the bottom layer
Basically, I want to put art onto the bottom layer, and thus I rerouted many signals so that the bottom is just mostly ground, on which its far safer to remove the soldermask

As you can see, there's only a bit of routing on the densest part of the board, but other than that its all gone!

Now time to actually get the art going!
By the way, most places were relatively easy, just switching the trace layer, but others required quite a bit of change.

I also tidied up this corridor so that the majority is in line with itself and thus doesn't affect the ground stitching around the ground plane too much.
2/27/2026 9:36 PM - Artwork
I added a bunch of new art, fully custom (except for the hackclub logo)
It took soooo long because in the beginning I was going to make a single PNG and import it, but I just ended up importing individual files and putting them in the right spot. It was just too hard to have one.


I'm going to clean up the backside a bit, but first I have to move all the Part-Identifiers so they are legible over this new silkscreen

2/27/2026 9:55 PM - Designators
I moved all the designators on the top side so that they are readable with the new wavy lines. The backside will be done tomorrow, when I redo that silkscreen to not conflict with the hackclub logo.

Anyways, there just was this one spot with the microSD card where it was tough, but most of the other places were easy enough

3/2/2026 - Artwork & Suture/Stiching Vias

First things first, I quickly fixed the artwork on the back side so that the hackclub logo wouldn't have lines going through it

However, on the front side I got feed back that the silkscreen was just a bit too much, especially with the ENIG art, and thus I removed it, even though I had moved all the designators...

The hard part
So that was all good, just a bit of easy artwork. But, the stiching vias were really bad this time, as for some reason the DRC was allowing the vias to be placed too close to each other, and then flagging them. So, I had to go through and remove each violating via by hand... (Some 50~60 vias)

btw my board now has 3000+ vias!!
Anyways, after that, there was the headers for the speed and 2 IO (buttons, etc) that were sort of conflicting with the battery, so I re-arranged them.

Debug Header
Reddit said that I should add the NRST pin broken out on the debug header, so that's what I did.

Required some wiring, including some on the bottom layer. (Which is my mostly empty one)
3/3/2026 - Added Another FPC Connector
This is basically just so that if in the future I want to have it connect to a daughter board, it will be able actually communicate. Because I didn't have access to STM32CUBEMX, I had to look through the datasheet to find out which pins (of the remaining) were the most capable so that I could actually use them. I settled on this setup:

Note: STM/ESP32_SCK is actually PA1
So now, I was left with the daunting task of finding the ideal order (shown above) so that it would actually be possible to route. On my 5th revision, I found that.
Now to actually wire this monster up

As you can see, the majority of the lines look alr, and I might actually just change the two weird ones (on the bottom).
The biggest problem, however, is the fact that this is already the most dense part of the board, where the four layers are most full, so wiring an addition ~16 lines is not going to be fun. Thankfully, if absolutely need be, I can always use inner 4, which is supposed to be GND only...
Some Time Later
Ok! I think I've settled on this setup for the pins:


Now time to route this all...
Routing More double checking!
Here's (hopefully) the final thing.


Now it's time for some routing
Routing
(some time later)
Ok so here's what I have!

This is one heck of a dense board, with all four layers being quite well used: (L3,L4,L6)



Not gonna lie, but that was quite a bit easier than I was expecting. I did end up changing a bit of the schematic, so that it would actually be nicely usable, so here it is!

(Mostly just switching around a few pins to avoid loops)
Yeah well that was a full days work! Proud of what I got done.
Oh yeah, btw re-added the suture vias (the ones that connect between layers)
ALSO I NOTICED THAT ALL MY JOURNALS FROM THE PAST DAY ARE ALL GONE?????????????????????????????? Like 10 hrs of work dissapeared!! Just a glitch
3/4/2026 - Tested (& Received) E-INK DISPLAY!!!
Firstly, I received the displays!!!!!





That last one might have looked weird ... that's my E-INK!
It was actually a pain to get it working. First, I connected it to my rpi Pico's wrong 3V3, so it wasn't getting power, then the library was really weird and glitching out. So, I changed my library to Waveshare's official one, however I messed up and installed the 2.13 inch display one, not the 2.9 inch one, so again it didn't work.
In between, I had messed up with which pins were SCK and MOSI, and kept getting the Bad Pin error, so that definitely wasn't nice.
All in all, it's really great it worked, but it took too much effort ... Hoping the LCDs are easier!
3/10/2026 - LCD & E-INKing
Alr, so, believe it or not, the LCD just worked first try!!!!!!
It was lowk so easy, took like 10 mins to wire it up (and double check and stuff) then I just found the official code and it just worked!!
While I'm at it, the screws also arrived, and hey, they fit!
And now the really time consuming part. E-INK custom text scaling!

Just so you know, I did, in fact, get it to work, but the thing is I just thought it would be as simple as just copying the LCD text scaler function over, and it would just work.
ye ofc it wasn't
But first, before I get to that part, I just wanted to talk about how I really goofed up in reading the E-INK code, so previously it would just drive the E-INK when the vertical_write() calls would come in, and not for the horizontal_write(), and I was really wondering why, and it turns out that I didn't change the SPI pins on the Horizontal class, just on the Vertical class.
Quick fix, just changed that, (but not so quick to find it, ofc) and now instead of waiting 10+ seconds for the screen to initialize, it just does so within a second!
So yeah, now that I had got that to initialize far faster (a problem I had been observing for quite a while, but was unsure of why), I decided that if my speedo was gonna be readable in bright sunlight, it would make most sense for the E-INK to be able to display speed, too.
Since, I needed it to be visible at a bit of a distance while shaking, I decided that I should find out how to make the text big enough to be readable. Hence, the problem
So I briefly touched upon how the LCD has a scaler, which it does, but the thing is that it's designed for a different setup, with colour displays and stuff.
So, I went looking online and I found this.
Basically, it does the exact same thing, but somehow works (after much modding)
"What modding," you ask?
Well firstly, I had to actually understand the code, and here's a rundown of what it does.
Put simply, it take micropython's framebuffer's built in font, and then reads it, by putting it into a new framebuffer and reading every pixel.
Then, it takes those readings, and draws rectangles that are the scale. So, if we were drawing to a 2x scale, it would draw the rectangles to be 2x2 pixels, instead of the default 1.
However, to do that, you must actually first make the framebuffer, which apparently can just be done by this:
me forgetting and searching my code
ok so yeah its actually really simple, just go
temp_fb = framebuf.FrameBuffer(temp_buf, width, height, framebuf.MONO_VLSB)
(if you have imported the frambuf library)
The problem just was that the code there used something different, and I have basically never used framebuf.
Alright so once that is there, there is still the fact that normally, 0x00 is black, and 0xff is white right?
Well for some reason that was flipped
I was writing 0x00, and I was getting white, while 0xff was giving me black...
(I now know that that was just because of the fact that the temporary framebuf [the one with the micropython default fonts] was the right one, but then it was being flipped by a stupid piece of [my own] code.)
By the way, writing these huge essays takes sooo long

Anyways, yeah that's about it! Now I have the text scaler!
Also, there is this really stupid thing that even if I clear the buffer, running buffer.Clear(0xff), it doesn't really, and I still have to run buffer.fill(0xff)
Oh well, also two more notes:
- THE PCB IS DONE PRODUCTION!!! (Now all I have to do is wait for them to assemble it!)
- I noticed that I can arrange the screens in a better way that should look better, And thus I'm thinking of completely remodling my entire thing... I should get that done real quick so that its done by the time the PCB arrives and I can just go print it and worry about debugging my PCB
NOTE: As you can see, this is a really long journal, and really long journals take a tonne of time to get done on their own ...
3/11/2026 - Re-3D modelling
I finally came around to do it, and so here it is!
Not nearly (or even close to being) done...



So yeah, white is e-ink, screens are obviously screens, pcb is also obviously the PCB, and the beige is the main case. The orange is the mounting pieces that are going to be used to mount the E-INK on, as it doesn't have useful screw holes.
However, it does have a pin header, that has this hole for it:

(The center is also shallower as there is the E-INK driving circuitry on the back)
Not much more to say, other then the fact that I'm worried about the GPS performance, as it has the batteries right under it, even though there is supposed to be a keepout. In addition, the E-INK is also quite close to it, but I think it should most likely be alright.
I'm really proud of the fact that I was able to finish this so quickly, taking just about two hours.
Next time, my plan is to add the main PCB mount, and bother with the screws.
I also will have to figure out the bike mount clips, but I think I'll do that part and the battery mount section later, after I get this part printed out and tested.
3/13/2026 - Pretty Much Finished 3D Model
So yeah, I Pretty Much Finished 3D Model.

All I really need to add know is the bike mount, and the screw holes to hold everything together!
So, pretty much, all I worked on was the back part.

This part will hold the PCB, in such a way that, with screw holes, will be able to also accommodated the batteries in a way that they can be removed for charging.

Where the more turquoise blue is is where the batteries are, and that is also where the thing will come off for when I want to recharge the batteries.
This might not seem like a lot, but compared to my last render, this whole workflow just happened much faster, and I'm actually quite proud.
And this is even after me having to redo much of my work, as V18 got deleted, so I had to go back to V17, and there was this problem:

Even though it's just the inside, I wanted that to also look nice, so that's why I wanted to fix that up, but the normal way I would do it in fusion 360 didn't quite work as it would keep say computation error. Thus, I had to go google it, and it wasn't of much help either. So, I just tended up deleting that bad part, and just extending the sides all the way to the cover that area.


Here's what the inside looks like:

Again, where its more turquoise is the battery area, whereas the bluer area is the other mounting part.
Just reworked some stuff for tolerances, so here it is:
PCB CASE:



FULL:



3/14/2026 - Worked On 3D Model: Bike Mount
I speedran the bike mount, the place where the speedo will mount to the bike

This is my first try at clip-on 3D prints, as the hole is a millimetre or two too small, so it's gonna be under a nice amount of pressure and thus gonna have a tonne of friction.


It's also designed to be screwed on right here to connect to the main body.
It should be noted that everything is a touch too small to make sure there is sufficient friction.
It is also nice that the bar is vertical,

So that the friction only has to prevent it from rotating sideways, not up and down.
I'm still a bit confused on how to connect it and everything, but its gonna probably be that the mount screws onto the back battery plate, which then has screws to the main body that are gonna have to be taken off everytime the batteries need to be charged.
Now all I need to do is add the screw holes, and I should be done!
3/15/2026 - Added: S O M A N Y S C R E W H O L E S
yep, that's what I did.
Firstly, there is the top side:

A TONNNE of screw holes. It should be noted that the screw hold on the right side, bottom left of the box, is just there for show.
There is nothing for it to dig into.

Back Side:
Here it is:


This all required me to cut out the bottom plate so that the screws can fit, as there is really only one screw type that is going to be used in all the holes.
The hard part: PCB Mount
When looking here, its hard to see:

But if I hide the PCB:

Not only did the hole alignment take so long, but also the fillets took just s o . l o n g .
s o o o o o o o o o . l o n g .
They kept on giving me errors, and all that nonsense. At least I didn't have to chamfer these holes.
Chamfering
If you looked really closely, you'd see the chamfering:

This is so that the screw can sit flat, and this too took so long, as google just couldn't tell me how to chamfer. (It was really simple once I found it)
Now all I need to do is the back mount!

PCB should be arriving tomorow!
bonus:
(main truss, looks so tuff)




3/16/2026 - PCBs ARRIVED: GOT THE LED TO BLINK!!!!!
Yep. Believe it or not, the naysayers and the haters have been shut down for good.
Those little-lings will now not be able to call me "one-who-cannot-program-an-stm32" as that is just what I did.
So it was a real mess trying to get everything to work, but really all I had to do was just ask @tty7 to tell me why it wasn't working, and all he had to do was tell me that you actually have to press the "boot" button for picky Ms. Windows to detect the STM32.
From there it was smooth sailing! more pain...
So now I had to figure out how to program it. @tty7 wasn't gonna be available for an hour, so instead I spent that hour in the googles. Firstly, I looked for a way to program it. Arduino IDE maybe? yeah no that didn't work
So I came back and found this epic video detailing exactly how to program the STM32 by USB
and From there it was smooth sailing! more pain...
yeah this time I had to like actually find out how the main things work, and considering the include the main() functions box was unchecked, that must have been a real nice, easy, time.
So once I rechecked that box, added these two lines of code:

and then flashed it like how the video said, and it just worked!
Lowk so happy
oh yeah forgot to say, my PCBs ARRIVED!!!!!
lowk so cool. I need to solder the ESP32 on, find libraries for my stuff and maybe not even use this, repent for making a micropython library for text scaling, and yeah.
Also, I'm paranoid I'm going to plug the battery in the wrong way and cook my stuff, so there's that. Haven't yet soldered on the battery holder, so once in a while power just cuts out. (the USBs don't provide my board with power)
3/18/2026 - Got CELL TO WORK & soldered esp32
Most importantly, I got the CELL STUFF TO WORK!!!

But anyways, the story
Soldering
Firstly, there was the soldering.
Preparations alone took me like 30~45 minutes, as I had to find a space, clear it, and then the difficult part: find the components.
So, the thing is, the ESP32s sort of disappeared, but were actually where I though they were all along, so minus 10 minutes (yes, I know, I'm slow)
Then, the same thing happened with the Flux. Like it shouldn't be so hard to find a big tube of orange goo, when I don't own anything orange.
(idk where it was now, and i lowk managed to lose it again ... nvm found it)
Anyways, then the flux tube wouldn't open.
At first, I thought that I stored it wrong or something, but no it was alright. So then why would it not just open?
because it was too cold
All I had to do was just add some heat, and voila it just opened
THEN, on the first board I guess I messed up and put too little flux or something, but when I soldered the ESP32, it just didn't go very well, and uhh i also tried pre-adding solder to the pads and that ended up just desoldering the decoupling capacitors.

Thankfully, the second set of stuff went much easier, and I just powered it and connected it and it just worked!!!!
The default firmware on these chips is cooked, so that's why I had to quickly flash new firmware before windows gave me a headache of complaints.
Alls well, though, and it just worked.
Next up, the switch.
Stupid Switches
Don't you just love alliteration.
Anyways, normally, when you slide the switch to the right, the right and center short, whilst the left two are not. Right????????
Nope. Not on this switch. That took me sooooo long to figure out why it wasn't getting power (the cell chip)
It took me so long to get the wits to actually bother to check the resistance and its behaviour... :(
Well, whatever, it works it works.
Coding & Cell
Ok, now the sad part.
When I was testing the stuff for voltage, I accidentally shorted the outside of the antenna to the VBAT pin, and who would think, the outside is connected to GROUND.
So well, what happens when you short the pins like that? Smoke. Lots of it. Well I was about to give up at that, but I thought: "hmmmm what if I try and plug it in anyways?"
So I did.
Plugged the battery in, and the STM32 turned on (it was programmed just to blink its LED)
Turned on the switch for the ESP32.
And.
IT ALSO TURNED ON!!!!
So basically, as far as I could tell, all was well.
But there was still the CELL CHIP. I knew for a fact that it was a very sensitive chip, so I was just given up on that and willing to accept a functional ESP32/STM32 devboard.
So, when I first failed to turn on the Cell Chip, I was sort of just confirming my suspicion. But, against all odds (after 30+ minutes) I decided to consult chatGPT.
I don't normally ever use it, as it hallucinates random code more often than useful code.
But it actually worked.

my shock ^^^
Anyways, that was absolutely great news. Despite smoke coming from my board, it just worked.
Then, I just fumbled with chatGPT to give me AT commands, and it, again, just worked.

However, there was this small problem. I was getting signals once in a while. However, thankfully, it was just warming up, and now it connects reliably.
The next problem was actually getting Adafruit IO to receive data, and that was done by just taking a look at what AT commands Waveshare uses on there SIM7080G boards, and copying them. (More like adapting them to my framework, as they were written for the Raspberry Pi)
And yeah, in short, that's all I had to do. I sort of glazed over the hours of pain that it took to get it working with the SIM module, but hey, all's well that ends well.
I also had the horrifying thought of giving up hardware. Super happy I didn't!!
3/20/2026 - Cleaning Up Basic Code & HTTP SADNESS
So I spent an hour or two cleaning up my code, so basically it's like clean and working well.
Whereas my previous code was comprised of functions and AT commands every where, my current setup is having a proper function, with some comments, but not very many.
def connect_to_cell(server, url=''):
global connected
if not powered_on():
power_on()
send_at('AT+SMDISC') #DISCONNECT FROM ADAFRUIT IO
send_at('AT+SHDISC') #DISCONNECT FROM HTTP
#send_at('AT+CGDCONT=1,"IP","simbase"')
while "99,99" in send_at("AT+CSQ", wait=.1): # signal
sleep(0.3)=
while "NO SERVICE" in send_at("AT+CPSI?", wait=.1):
send_at("AT+CSQ", wait=.1)
sleep(0.3)
send_at("AT+CGACT?", wait=.1) #active?
send_at('AT+CGDCONT?', wait=.1) #ip & stuff
send_at('AT+CGPADDR=1', wait=.1) #connect to wifi
send_at('AT+CNACT=0,1', wait=.1)
send_at('AT+CACID=0', wait=.1)
if server in ['io', 'adafruit', 'mqtt']:
send_at('AT+SMCONF="URL","io.adafruit.com",1883', wait=.1)
send_at('AT+SMCONF="CLIENTID","esp32-sim7080g"', wait=.1)
send_at('AT+SMCONF="USERNAME","space_coder"', wait=.1)
send_at('AT+SMCONF="PASSWORD","aio_ttzT34pSKp3WBIRbIASSLQfqNBqA"', wait=.1)
send_at('AT+SMCONN', 1)
if server in 'https':
send_at('AT+SHCONF="BODYLEN",1024', wait=.1)
send_at('AT+SHCONF="HEADERLEN",350', wait=.1)
send_at(f'AT+SHCONF="URL","{url}"', wait=.1)
send_at('AT+SHCONN', 5)
connected = True
(cleaned up version)
That, above, is my connect function, and as you can see in addition to cleaning up the code, I also figured out how to speed it up so that it works well. Whereas my previous function would take well over 15 seconds, some two seconds per command, now it takes about 3 seconds to connect to Adafruit IO, or about 7 seconds to connect to connect to a webpage (I'll get to this later).
I also set it up with the DTS and RTS commands, so that if I want higher bauds, in the MHz range, I can.
Furthermore, I re-setup the UART, as previously there was a built in slowing function, adding about 1 second to every read.
HTTPS
So this part was just like the others
I wish.
The other cell features were actually pretty easy, but this time chatGPT started overly hallucinating, so that is why it took so long.
Firstly, there was the pinging. To get it to send a ping, I had to find a good source, as GPT wasn't working. This one had a good chunk of the stuff I needed, but it was a bit confusing, as the APN was different on their page. All in all, the pings were quite easy, and then there was the actual reading part.
Reading is as simple as sending send_at('AT+SHREQ="/",1', 10) and then send_at('AT+SHREAD=0,528', 10)
But the connection part is a whole other mess.
So as you can see, my current connection function asks whether I want to connect to IO or HTTP, and that's not because of speed or anything. That's because 90% of the time it cannot connect to both. So the correct course of action would have been to disconnect from the IO, as I now do, and then connect to the HTTP, however I didn't know that, so I was just like stuck and wondering why my connection code wasn't working, but the example did.
Once I figured that out, it was quite easy, really. Just make sure to disconnect, and all should be well.
The Pain
So, I decided that I was going to clump the functions and clean them up so that it won't waste time and data connecting to the MQTT if I wanted the HTTP, and vice versa. So, I put the code in and it just didn't work. I debugged it for a solid hour and I finally found the problem. I had commented out the lines of code that told my modem how much of a buffer to assign to the read function, and voilร , nono functioning.

Uncommenting those lines pretty much fixed the entire problem, and yeah.
3D printing
I went to the library today to see if they had a printer open, and, even in this weather (its raining/cold), there was not a printer (out of 2) that was open.
Also, I'd just like to note: picture are going to be hard as I don't have much to show, other than code, and I don't have a camera.
p.s. This journal alone took me 17 minutes of locking in/focusing.
3/21/2026 8 PM - Finished 3D Model
Yep!
I fixed up tolerances, and added the final screw holes!






That means 6 holes here (3 both sides)

which connect to the battery clip here:


Which connects to the body here:



This side didn't have enough space, so the chamfer and screw hole actually goes a bit onto the sides:


I also fixed up the display holes so that they would actually hold the screw, and not just let it slide right through.
3/21/2026 9 PM - Got 2x 3D printed parts
I went to the library and just sat there for ~30 mins waiting for my print. Gonna have a lot more of that...
Anyways, I just barely missed my bus home, so I had to wait an extra 30 mins :(

I'm not adding the 2hrs.
3/21/2026 10 PM - Got Screens To Work
So yeah, basically I got them to work!




So, I had already got the screen to work, but that was by connecting them to my rpi Pico, which apparently has more RAM than my ESP32 (not PSRAM, RAM)
So I got this lovely error:
MemoryError: memory allocation failed, allocating 153600 bytes
New Library
Ok, well the official waveshare one didn't work, so I thought of just googling the chip, the ST7789T3. And right there, I found the ST7789 Library, something that I have even used before!
The thing is, I'd forgotten a bit how to use it, but mostly my brain was cooking, and I flashed the new micropython file and
...
...
LOST ALL MY FILES :(((
It wasn't that bad, as I had my main file backed up to my computer just in case, so thankfully, it's mostly all well. Just for my own stuff, I'll put it at the end of this journal too.
So, with this new (outdated) micropython file, I tested my original code, and there was just a tiny difference, with the way you assign pins for uart, but other than that all was well.
So that still wouldn't explain why it wasn't working.
Well, turns out, I wired my display connector wrong. What I put as pin 1 was actually 18, and vice versa all the way.
Thankfully, the geniuses at waveshare thought that someone might be that stupid, and thus they made their conector have contacts on both sides, so you can insert the cable either way, and swap it around so that all is well!
And after that, all went quite well.
Anyways, I'm a bit concerned about the amount of power the cell chip is drawing, as its quite a bit. In all of this testing, the voltage has dropped by ~0.3V (so maybe ~.1V per hour? I guess that's not terrible) from 4.008V to 3.758V
I really wonder why I'm so slow at journalling: this one took me 15~20 minutes
3/22/2026 6 PM - Tried To Get Arduino IDE
I know that all my previous journals always had stuff getting working, but this time it was very different.
Arduino IDE logged 40 minutes of me trying to get it to work, but if you include googling, cubeMX programming and all of that (not-so-fun) stuff, it was a bunch more.
Anyways, what I basically did was just spent my entire time seeing why my board wasn't showing up, while there were others, and I'm not even really understanding why, but my chip just isn't supported, so I'm going to have to use HAL and STM32CUBEMXIDE, which is even harder :(
lowk i just wish we could get micropython.
3/22/2026 9 PM - Got Two Screens to Work
Spent a bit of time modifying the code to drive both screens simultaneously, and here's the result!
Only one minor hiccup, I accidentally defined two different SPI lines for each, using the same pins, and micropython didn't like that.



.jpg)





WIN20260322195041_Pro
3/23/2026 - Worked on connection script optimisation
I made a new connected function which basically uses AT commands to detect if its connected so it doesn't reconnect, unless the connect functions' force=True (default False)

I also implemented it, and also did a 70 minute test with a message outgoing every 2.3 seconds to Adafruit IO, and it ended up using about 200kb (for ~1700 messages)
Looks pretty good to me!
I also stated implementing the show flag, to hide outputs for useless commands, such as AT+SMSTATE? and AT+SHSTATE? as the connect function will handle and tell me what happens.
3/27/2026 - 3D Model Fix & Coding
So I got my parts, but as they required support material, they are all cooked. Well that was a nice quick fix, but I lost an hour or so in going to start the print and the getting them.
But I just realized, right as I'm writing, that I forgot to add a hole for the FFCs, and so I'm gonna have to route them in the most awkward weird way. I'll fix them in a later revision.
Since I have (most of) the parts, I can now see that this thing is gonna be quite fat, and fingers crossed it still looks alr.




And after that I spent 3 and a half hours coding to make the text as big as you can see above.
This including learning how to make custom fonts.
The custom fonts were the hardest part.
The custom fonts were the most time consuming part.
The custom fonts were (pretty much) the only part.
Uhh no actual there was also one small other thing.
Anyways, here's my list of errors, after spending like an hour trying to force my brain to understand how the font generation works. By the way, it doesn't help that the docs and everything are pretty much non-existent and really old.
#1 Wrong function: I used monofont2bitmap, which isn't compatible, whereas font2bitmap is. ????????
I still had to debug it to give me an output, which included installing a library, finding out it was too new and didn't have some functions, vibecoding a fix, and just wondering why????
In addition, its debugging was ... not so good?
Spent far too long trying to see why it kept giving a (seemingly) random error, and as it turns out, I needed the -c or -s flags to tell it which chars... WHY CAN'T IT AUTO DO THE ONES I NEEEDDDDD????????? :(
And after all that, I was wondering why the write function was failing. Firstly, it was because of using the wrong function (more later) and because this function gives an incompatible bitmap. How nice.
#2 Right function, wrong writing method
So, I go in and use the text function, as I've always done, and it throws a random error. No clue why. ChatGPT gives me the right answer, but I thought it was hallucinating. So, digging through the very outdated docs, I actually find it! The correct function was the write one.
and that pretty much would have fixed it, if not for me being in the wrong file
#3 Wrong File:
So like it should be really hard to edit the wrong file, right?
Well no. When you have two files, one for the old code and one for the new, that can happen. I used the write function with the in-built font (which requires text) and it didn't work.
How nice
Minus an hour (like 10 mins) of my life I will never get back.
So, once I find that out, I immideately rage-close the file, and well everything went nice afterwards.
I just spent a bunch of time getting the font size right, getting it centered and all that stuff, and getting 1 to become 01, etc.
Well I probably forgot smth, but that should be the gist of it!
3/28/2026 6 PM - Threaded Holes
Well that was a lot of holes. I ran the screws in the holes to thread them. For the holes that were not so usable, I first drilled out the support material, and then threaded them.
I also mounted the screens onto the main trus, but I have a problem that the holes are all too big on the main truss, and thus its basically impossible to keep the e-ink on, but I don't quite have enough time to fix it, so ig it'll just have to work for now. I'm going to get the last parts today, (if they didn't fail) and I'll thread them and everything for tomorrow so I can go test it!
I'll just quickly ( ** hopes ** ) finish them off.
I keep forgetting to get receipts, so the majority of the prints are coming out of my money. Already burnt through $10.


3/28/2026 10 PM - Software and assembly
I got the rest of the components!
After removing supports, here is the structure!







In addition, I worked on the software, which is why
(i'll add pictures next time if i remember)
I have new software running that accurately uses gnss time and speed for display. It took me a while because half-way through, my computer glitched out, asked for 60 gigs of ram, and crashed in my face, taking with it some of my progress.
I have not only the speed (this time from gps, not just a placeholder), but I also have the time displaying and the battery percentage. The battery took me a bit because I had to find a way to decode the easy-to-read but not parse text from the cell chip. (The cell chip can measure battery voltage and percentage)
The time was its own headache because it just wouldn't sit right and like I had to find a way to change the timezone from utc. I installed a library that I couldn't figure out how to use, so my current setup is just the utc time, minus the offset, modded by 12. So, if its 0 o'clock utc, i'll get a negative number that will then mod back to a positive one, and all is well. It just can't handle dates at all, but hey, I only need to know the time, if that, while on a bike ride.
I will be prepping to submit this, even though the STM32 portion isn't really done, as the deadline is coming up way too fast.
3/29/2026 - Over Discharged my LIIONS(to 2V) & software & testing
I wrote some software and stuff to show the satellite information to see how many satellites are visible and connected. In addition, it also tells you, for each satellite, the signal-to-noise ratio, snr, and somehow I am getting ~49 outside, with high 40s for like 5 satellites, with a patch antenna. As if this isn't crazy enough, my HDOP is like ~1, and I am getting like ~35 near a window (inside) and like 30 near my monitors.
And yeah I left it to run a long test, and the battery really got over-discharged, as the buck-boost converter was still supplying my esp32 and screens with stable 3.3V. My Cell chip, however, was unhappy and shutdown. Since I was away, however, it went on for like either 2h10 ish or just 10 mins after the cell chip cut out at probably 2.5V.

just some data: time, displaying percentage, %, actually percent, time stamp utc, pin value of the shutdown pin, newline, time it took to update LCD, check uarts, issue command for battery percentage, etc.
I also did some tests outside, running around, going for a walk and all that, and I am happy to report it looks very accurate. I was getting, on the high side, about 22 km/h and like 5 km/h for my normal walk, which lines up with my estimates.
3/30/2026 8 PM - PICS!!!! & Assembly
I assembled like 10 screws to just have like the bare minimum structure, but I had to disassemble it before going somewhere (to turn it off) and well here it is!













And here is the bottom side with the clip-on and screw-on-able cover off. Notice that one battery clip is not soldered on because I forgot to include it in the BOM and like yeah.








Fully Assembled (minus the battery-clip) and the wires stuffed in.





I really need to make some cable-management slots in the new truss, but it pretty much works as is rn.
The battery-clip and stuff are a bit tight and hard to put together as I have to undo it all to turn it off, thus I don't have the pictures rn. It needs some software to fix that so that just hitting a button (which needs cutouts) will shut it down and power off the ESP32.
3/30/2026 9 PM - Updated Bike Mount Part to Fit.
Yep, that's exactly what I did. I extended the part, resized the holes to become perfect (hopefully) and I also made is so that it isn't the thick diameter all the way, just for a small area, which of course is filleted.

3/31/2026 - Got a 3D print, assembled, updated code
ok well that was ... a bus ride
so basically, i had to go get a 3d print for my #blueprint project, so I basically just went somewhere, and the thing is, I thought I started the print, but it didn't actually start, so when I came back to check on it an hour later, I saw it wasn't there. (It's a 30 minute print)
So, I asked the staff if they took out the part, but no, they couldn't find it. So, I'm like panicking, where did it go?????
We go to restart the print, and this time I sit there for a minute, and I see that the print doesn't start. We try different SD cards, and all that, still no progress. Then, we literally just restart the printer, and it all just works. 35 minutes later, I am running to the bus stop, in pouring rain. Like actually pouring rain
Like soooooo pourring that the water is bouncing like 5cm off of the grass
well that was how long it took to get the print and stuff, but well yeah that's life ig. (total unlogged (but journalled) time for print is now ~6 hrs)
Anyways, I also worked on the assembly, and I'm glad to say it works!








The screens are just flipped, but that's a simple 0 --> 2 change.
I also worked on getting the gps to work a bit faster, so now it's updating at 5hz, vs 1hz before. This way, it will feel more responsive. I was wondering why the command wasn't echoing, and apparently it must end in /r/n, and also apparently you need to tell it which pin is the tx pin. Huh, wouldn't have guessed.
Anyways, once I figured that out and got it to work, the satellite info was becoming fuzzy and changing too much so I made it only display the first 5 satellites (in order of signal strength) and I think all is well!
That assembly took a while as a component was a bit too big and thus flexed outwards so that the screw holes wouldn't align.
4/3/2026 6 PM - Assembled/Tested [slight retro]
Today was probably the nicest day this year, so far, and thus I took that opportunity to take it out for a bike ride. However, I had to assemble it first. There was mostly just the thing that I assembled it, but then I remembered that I had to flip the screen (in code) so I had to disassemble it to reprogram it. After re-assembling it, for some reason the GPS hung out, and thus I started to disassemble it. Lucky me, for some reason after just removing the first screw, it looked like it restarted, and started working.
However, this was the first sign of foreshadowing
I simply thought "Ooh, how lucky am I!" and continued on.
Once it was on there, I took it out for a bit, just down the street, and ... the wind! It was soooooooo windy. The advantage was, however, that on the way back I easily clocked (what my speedo says) around 32~34 km/h
The bike mount part looked a touch angled to me, however I just ignored it, thinking all would be well.
At some point, I was also thinking of what the biggest reason for it to fail would be. As I only had 3 screws on the bike mount (out of its designed 6) due to the mount flexing too much to fit the rest in, I thought that would be the biggest point. All other pieces bore relatively less weight, and were pretty sturdy, with several screws. The battery clip, for example, was mounted with 8 screws. (Believe me, it's a pain to put in on and take it off)
Anyways, I thought something along the lines of if a screw strips, I'll just take a look at it later. Definitely not foreshadowing.
As all was going well, I went for a longer bike ride, a 15 km loop. And it was here where disaster struck.
I was about 500 meters in, where I noticed that there was quite a bit of a flex/offset on the bike-mount part, but I thought that it was probably just rotating, as perhaps the friction was a bit low.
I also suspected that the wind, which was easily gusting to ~50km/h was also perhaps to cause. Now that I think of it, maybe I should have paused to see if all was well, but uhhhh I was sort of stupid.
So, I started noticing that, at the 3km mark, the screen was wobbling enough that the battery clip (which's spring conducted so much power that it shorted together [this was when I accidentally shorted the antenna to the power pin]) was starting to give way at some times, resulting in the cell chip losing power without it shutting down. The datasheet clearly says not to do that as it might corrupt its software, and thus I thought of turning around. However, as I had already scaled the uphill and the upwind part, I didn't feel like undoing all my progress. I thought that I'll just go slower, feel less bumps, and continue on.
I continued on to the 5~6 km mark, and disaster struck.
It was on a downhill, with the wind helping me, and even though I was trying to go slow I was probably clocking 20 to 25 km/h. And it was here that all three screws stripped. And the entire assembly, screens-first, fell onto the asphalt.
Thank goodness, however, it fell at a tiny bit of an angle so that the plastic absorbed some force, and the rest was on the screen with the screen protector on. Thus, after some cleaning, the screens look perfectly fine, but the main truss (that I was going to reprint for some issues anyways) has dirt on it and is a bit damaged.
Now, the painful part. I had to walk my bike for the 6km home, as the wind was gusting too hard for me to bike with a single hand. (I had to hold the odo in one, ofcourse)
Occasionally, I would take the risk and get on my bike, but everytime the wind became to powerful I would get back off.
Thankfully, after getting home, I was able to test it and confirm that all functions work.
JOURNALLED TIME
As this included coming back home, I will NOT be logging that time, but the going there (testing) time and assembly is be logged. Journaling took 30 minutes. (I am not kidding I seriously write really detailed stuff.

Google maps shot of where it failed. I did not have my phone with me for the entirety of this ride.
4/3/2026 7 PM - PROGRAMMED STM32
So if you read my last journal, you'll know that I was having a bunch of trouble with the battery bouncing around and messing with the cell chip, and thus in the meantime I've worked on some code in the STM32CUBEIDE to allow me to basically short out a set of pins to shut down the ESP32, and another set to wake them up.
Basically, the wake up is quite simple, but the shutdown just signals the ESP32 to stop doing whatever it is, and then the ESP32 will shut down the cell chip. In turn, it will tell the STM32 that it's done, which will then cut off power to the ESP32.
I had a few problems where the STM32 would keep detecting a false low and turn on the ESP32, and even when it returns and tells it to turn off, it would flash off for like a millionth of a second, and then turn back on, a time period which was enough for just the capacitors to smooth out.
Anyways, I still have no idea why that happened, and my current work around is to just use a different pin. It could be that for some stupid reason I wired it up to two different IO pins on the STM32, and now I'm stuck with that pin not working. Thankfully, I designed so extra screw terminal blocks, so I will be (most likely) ok.
I also had a bit of a problem with me thinking that the terminal block was flipped around, so I shorted two IOs together, not an IO to ground. Simple fix, and all went well. (After like 10 minutes of debugging why its not working)
Anyways, I also had some timing off so that it would wake up, and the ESP32 wouldn't have a chance to tell the STM32 not to shut it back down, and the STM32 would do exactly that. Again, simple fix (long time to find) and I just added a bit of a delay.
I still don't have the touch/ina226/imu/mag wired up, but it all will be slow, and I will most likely just submit it up before that as I will have to find libraries and stuff.

I was thinking, I might just make it so that the same button will turn the ESP32 on or off. Hmmmmmmm. I think that might be a bit hard, but oh well.
4/4/2026 - Failed to get ina226 to work even after spending ~7 hours
Ok so basically, the tldr is that I tried to get libraries to work with the STM32 but couldn't understand it, so I tried getting the Arduino IDE to work again, it worked, but some very important pins like PC5 were not supported and thus I can use it either. Next steps are to either try getting my real board in the STM32 interface or building a simple custom library in STM32 HAL.

No, I did not mean PC_5
Now the long story.
So, as I said, I wanted to get the other sensors/E-ink working.
That is basically all that remains to get the entire thing good, and functional.
First up, I tried the E-INK, as there supposedly is support for it to work. However, as it is typical waveshare, so I can't understand anything.
Well, I did understand a bit. So it seems like its set up in having .h and .c files all together in a folder, but they keep pointing to each other so its quite hard to understand. Not to mention like all the libraries are there, including those for like every single size they have ever made.
Well, that didn't work well, so now I decided to go and try the INA226 libraries. And, guess what. Not a single library I could find was recent, except this one that is some 10 or 11 years old.
I guess I'll have to try that later, but if even the INA226 doesn't have one them I'm gonna be pretty surprised if the other sensors have them.
So, I sort of thought, what if I tried to get the thing working with the Arduino IDE.
And, believe it or not, it actually worked.
If you look at my journal a bunch of time ago, you will see that I said that I failed to do it. I still have the problem that the Arduino IDE doesn't officially support my board, but I was able to find a close board to that that allows me to toggle the first pin I tried, the LED pin.
So, with the Arduino IDE, I was able to actually program the board to flash an LED.
However, when I tried moving the program over, it didn't work the best. It gave me an error with pins PB12 (not too important) and PC5, the most important pin. PC5 is the pin that toggles the ESP32's power.
Whereas my current board goes through the following phases:
I litterally wouldn't be able to turn the ESP32 on/off.
I had spent about an hour thirty at this point, and so I went off into google to find out if I can get that pin to work

btw, I had already had some trouble with the pins not mapping properly, so I had set up some programs to guess and check the pins to see which is which.
for (int i=10; i<50; i++) {
//Serial.println(i);
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
for (int i=30; i<37; i++) {
Serial.println(i);
pinMode(i, OUTPUT);
digitalWrite(i, HIGH);
delay(2000);
digitalWrite(i, LOW);
delay(1000);
}
^^ this was my code to guess and check the PC5 pin. I went through all GPIOs that were close, and the ESP32 didn't get power :(
So, after that, I went around looking for the stuff. I found out that the problem was related to the Q port on the test board that I was using, and the options without the Q ending didn't have PC5 to begin with.
Basically, the Q port uses the pin mapped to PC5 normally as I think an SMPS pin, so it literally won't work. It only now makes sense why it wasn't working with that wide array of test pins.
If you look, the most frustrating part, is that the Arduino Core github repo actually has the STM32VGT folder, with the proper mappings for my board, but I can't select that from the Arduino IDE, and thus I can't use the Arduino IDE.
ofc, this took a lot longer than you would think, as like you actually have to find out what is up, including looking understanding how c++ works as a total beginner (I have literally never used the Arduino IDE nor the C++ or C, except like one program for SOM)
So yeah this is where I've spent my last like 6~7 hours. It might not seem like that, but it really does take a long time.
And now what should I do next.
As I briefly touched upon in the TLDR, I really only have two options. Either figure out if I can get the correct board settings with the Arduino IDE, or build the custom libraries in the STM32 IDE. Both options are a real pain, but here are the pros for each:
| Arduino IDE | STM32CUBEMX |
|---|---|
| Simple to use if I can get it to work should not require me to re-write libraries for each of the (only) 3 sensors and the E-INK | Gives far more control and easier to optimise; can change the clock speed and other such stuff far easier |
| Simple to use, well supported | Certain to work, but a lot more work |
| Easier to program, included debugger so that I can actually have a working serial monitor to see what is going on |
So, basically, the thing is do I want to have the chance of wasting my time in trying to get the Arduino IDE, while sacrificing performance, or do I just want to go for the STM32CUBEIDE, which will work for sure, but is definitely going to be a lot more work. Well I guess I have a lot to think of tonight.
As always, to make sure my journals are as detailed and engaging as possible I have invested a lot of time into it, with this journal taking me ~35 minutes. I am logging this time.
4/5/2026 3 PM - Made Code Simpler; Learned Booleans; Commented Code
Yep. Did you know that C, natively, doesn't have booleans?
Well, quick fix, I just included <stdbool.h> and then it worked. Still really feels weird that it isn't the same color as the int or voids.
I needed this boolean, because I wanted to keep track of weather or not it has actually turned on or not, so that I can just use the same IO to both turn it on and off, as it now is. However, it really is a pain to understand what is happening (even if it is my own code) and thus I decided to comment it. Like pretty much every line has a comment. Take a look:

while (1)
{
// TURN OFF ESP32 - if ESP32 sends turn_off signal, turn it off.
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10)) {
HAL_Delay(50); // Pause to see if it was accidental
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10)) { // Re-check to see if it is still on
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_RESET); // Shut it off
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_RESET); // Turn off LED
HAL_GPIO_WritePin(ESP_TX_GPIO_Port, ESP_TX_Pin, GPIO_PIN_RESET); // Stop telling the ESP32 to shut off
esp_on = false; // Register the ESP32 is now off
HAL_Delay(100);
}
}
// TURN ON/OFF ESP32
if(!HAL_GPIO_ReadPin(GPIOB, OFF_Pin)) {
if (!esp_on) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET); // Turn on ESP32 Regulator
HAL_GPIO_WritePin(ESP_TX_GPIO_Port, ESP_TX_Pin, GPIO_PIN_RESET); // Stop telling the ESP32 to shut off
HAL_Delay(1000); // Wait for ESP32 to initialize, avoid turning it back off
esp_on = true;
} else {
// If ESP32 is on, send it the signal to turn off.
HAL_GPIO_WritePin(ESP_TX_GPIO_Port, ESP_TX_Pin, GPIO_PIN_SET); // Send off signal to ESP32
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET); // Turn on LED
}
}
Feels so profesional, doesn't it ^^
Next up, I'm going to rename all of those pins and stuff so that they actually make sense, and render my comments mostly useless.
4/5/2026 9 PM - Further cleaned code; Added anti-theft tracking.
Here's pretty much the same code as last time, but as you can see its cleaner
while (1)
{
// TURN OFF ESP32 - if ESP32 sends turn_off signal, turn it off.
if(read_io(RX)) {
HAL_Delay(10); // Pause to see if it was accidental
if (read_io(RX)) { // Re-check to see if it is still on
if (HAL_GetTick() < esp_on_ticks + 100) {
write_io(ESP_PWR, OFF);
HAL_Delay(10);
write_io(ESP_PWR, ON);
HAL_Delay(wake_pause);
esp_on_ticks = HAL_GetTick();
} else {
write_io(ESP_PWR, OFF); // Shut it off
write_io(LED, OFF); // Turn off LED
write_io(TX, OFF); // Stop telling the ESP32 to shut off
write_io(ESP_UPDATE, OFF);
esp_on = false; // Register the ESP32 is now off
HAL_Delay(10);
}
}
}
// TURN ON/OFF ESP32
if(!read_io(ON_OFF)) {
if (!esp_on) {
write_io(ESP_UPDATE, OFF);
write_io(ESP_PWR, ON); // Turn on ESP32 Regulator
write_io(TX, OFF); // Stop telling the ESP32 to shut off
HAL_Delay(wake_pause); // Wait for ESP32 to initialize, avoid turning it back off
esp_on = true;
esp_on_ticks = HAL_GetTick();
} else {
// If ESP32 is on, send it the signal to turn off.
write_io(TX, ON); // Send off signal to ESP32
write_io(LED, ON); // Turn on LED
}
To do this, I (with like no knowledge of C) made two custom functions, one to read a pin and the other to write to a pin.

They do this by taking in an array to tell it which pin and then just writing to that pin. However, thanks to the #define command, I can just pass in a string (like RX or TX) and it will just parse it. Take a look at my above function for more clear stuff.
Ofcourse, I, mr python user, thought that C would have lists, but apparently they don't. Instead they use arrays, and as far as I can tell they can only have one type of thing in an array, so it basically can't work.
I need a GPIO_TypeDef object for the first one (the 'port') and a uint16_t (which i guess is just an integer) for the second one.
Thus, I had to make my own struct thingy or something. (This is one of the only parts made by AI)
typedef struct {
GPIO_TypeDef *port;
uint16_t pin;
} io;
// STATES
#define OFF 0
#define ON 1
// Pins
#define TX (io){GPIOA, TX_Pin}
#define RX (io){GPIOA, RX_Pin}
#define LED (io){GPIOE, GPIO_PIN_2}
#define ESP_PWR (io){GPIOC, GPIO_PIN_5}
#define ON_OFF (io){GPIOB, IN2_Pin}
#define ESP_UPDATE (io){GPIOA, GPIO_PIN_1}
#define wake_pause 1000
but hey, it works! so no complaint from me.
Anyways, after that I played around a bit with the clock, so basically now I know how to change the clock speed, and I configured it to use the external clock I put onto the board (its a 12MHz one, so CUBEMX had a touch of a problem)
Then, to save power, I reduced the clock way down to just 1MHz, which should be drawing just 20ยตA, but for some reason it isn't.
At 160MHz, it was drawing ~16 mA, so about 100ยตA per MHz, (5x more than datasheet), but this was before I added code to run with the timers, and maybe the external clock is also drawing power.
Speaking of which, you may have noticed that there was some code about restarting if I don't get a signal.
Sometimes, what happens is that the ESP32 fails to boot up, so if after about 1000 ms the STM32 is still getting a 'shut down' command, I'll know that it isn't working properly and I'll turn off and on the power on the ESP32 to reboot it. (This has happened quite a few times for some reason)
In doing so, I decided to tune the amount of time it waits, for whatever stupid reason, and just decided to consult GPT. It said that it takes about 50 ยตS for the code to run, so I started out with a wait of ~1 ms. The light just flickered, with power turning on and off rapidly, so I tried 50ms. After several tries I just decided to go to 2000ms (which worked) and now I have settled on 1000ms.
Just a reminder that changing code on the STM32 requires putting in to flash mode, connecting the programmer, building, and flashing, alongside actually testing it, which easily adds up.
After this, I worked on the anti-theft stuff, which took most of my time.
Anti-Theft stuff
So, to track if it has been stolen, my plan is pretty simple. Every 10 or so minutes, wake it up, send the exact location to my MQTT broker, and tada. I can track exactly where the tracker is.
To do this, I had to like actually learn about timers (the above actually came afterwards, but hey this is more organizes)
Apparently, the STM32 has a bunch of timers that you can use, and I tried configuring one for my use, but it just wouldn't really work.
I googled my problem (again) and finally I was able to find out that the stm32 also has a function that will allow you to know how many seconds it has been since the code started executing, and you can just easily use that as a ms tracker.
Well, that is exactly what I did. At first, it was set to just be that the LED would (in a non-blocking way) turn on and off every 5 seconds, and after that success, I started building.
My STM32 code just ended up being quite simple, however my ESP32 code was far more complicated.
if (HAL_GetTick() % 600000 < 2000 && !esp_on) {
write_io(ESP_PWR, ON); // Turn on ESP32 Regulator
write_io(TX, OFF); // Stop telling the ESP32 to shut off
write_io(ESP_UPDATE, ON);
HAL_Delay(wake_pause); // Wait for ESP32 to initialize, avoid turning it back off
esp_on = true;
esp_on_ticks = HAL_GetTick();
}
What that code just does is it turns it on, turns off the command io to shut down, turns on the 'express boot' io and then just waits for it to initialize.
In essence, my ESP32 code is just as simple as the following:
else:
try:
i = 0
while i < 1000:
while gnss.any():
data = gnss.read()
for byte in data:
stat = my_gps.update(chr(byte))
sleep(0.01)
i += 1
if my_gps.satellites_in_use > 4:
break
sleep(0.5)
send_message(f"{my_gps.latitude[0]}", feed='latitude')
send_message(f"{my_gps.longitude[0]}", feed='longitude')
send_message(f"{my_gps.satellites_in_use}s {my_gps.speed[2]}km/h {my_gps.hdop} hdop {int(send_at('AT+CBC', show=False).split(',')[1])}%", feed='other_info')
sleep(0.5)
except Exception:
sleep(2)
power_off(False)
power_off(False)
However, to do this it required basically a complete restructuring of the code and also like actually remembering how my code works.
There are also some other parts of my code, but this is the biggest part.
The biggest part was making sure that the GPS actually had a good set of data, and, at the very minimum, wasn't giving me 0s before sending the data.
While I was testing, I noticed that the ESP32 was reconnecting to Adafruit IO for some reason during every send, even if it was connected, at to figure out why I sort of fell into a rabbit hole.
It turns out, the problem was that my code for sending the info was lagging, and that was messing up the code that checks if the modem is connected.
I wish it were an easy fix to do that, but I had to like write my function to now check when the modem has returned the correct thing to then send the data after that, and then write the code, wait for the modem to get back, and then only continue with execution.
Well, for some reason that journal took even longer than the rest.
It was ~40 minutes.
I'm probably missing something, but I really can't spend anymore time journaling โ it's getting far too late.
4/7/2026 10:04 PM - Filmed, Edited and Finished Demo
DEMO!!!!
Yep, I think I got it done. Took me a bunch of time for me to assemble all ~30 screws, but hey, take a look ^^^
I even took the care to mute us out and replace it with some lovely music that isn't synced and definitely didn't take me just 10 minutes to put on there.
I sort of did have to like install DaVinci resolve, figure it out, and then like actually export it, so...

In addition, I also used up one of my three free monthly downloads for music (from upbeat) so there was also that.
(btw that was like my third shot, and I had to disassemble it all over again to be able to update the code.)
4/7/2026 10:21 PM - Debugging Random Shutdowns.
So the thing is, there is an off chance that my board might be like glitching, because Adafruit IO stopped receiving messages at 3 AM, for whatever reason. When I woke this morning, for some reason the ESP32 was still on, so I just rebooted the board. Since then it's worked just fine until now (10pm) and yeah.
In addition, there is also the problem that it once in a while it will turn on but not boot, and that's why I have some code on the STM32 to reboot the ESP32 if no code is running (it doesn't drive the comms pin high) within a second or two (like I mentioned before)

code for it it isn't booting properly
if (esp_on && esp_on_ticks + 60000 < HAL_GetTick() && update) {
write_io(ESP_PWR, OFF); // Shut it off
write_io(LED, ON); // Turn on LED
write_io(ESP_UPDATE, ON);
HAL_Delay(1000);
write_io(ESP_PWR, ON);
HAL_Delay(wake_pause);
esp_on_ticks = HAL_GetTick();
}
I inturn added this tidbit so that if it's been over a minute with no return on the send messages function, then it will also reboot the ESP32, as per normal stuff, and if it returns too quickly or something it will reboot again.
Anyways, there is also a bit of code on the ESP32 side for that too, but that's just a simple log file, which is putting in numbers for some reason. I'll figure that out later.
with open("log.txt", "a") as file:
file.write(f"{machine.reset_cause()}\n")
Anyways, even after some serious code-look-over-ing, I still have no clue why it won't work. Ughhhh.
Maybe if the same issue happens again at 3AM or smth then I'll know, but the thing is that it will just eat it up and forget about it.
Hmmmm.
Also, my clock isn't accurate at all, and so it's actually like once every 9:45 or something, and thus like it just feels off. I'll have to find a fix for that too.
Anyways, for that last journal, you may have seen my thing about the free song download, and what happened is that I got a copyright notice, and so I panicked, but it was quite easy to fix.
Well, another thing, just as I'm journalling, this is the second time its woke up to transmit data.
4/8/2026 9 PM - Failed To Get STM32 USB Working; INA226 Code
Ok well today was pretty much a total failure, as I spent all my time trying to get the INA226 to work, for which I needed to have USB communication or something to actually get the data off.
So, first things first, I looked through the libraries and really there only is one that can work, and I tried copying things out of it. I copied the #defines and some really basic functions, just the read voltage and read shunt-voltage. However, it gave me the error that it couldn't find an I2C bus: I hadn't set it up, and that it couldn't find the float32_t typedef. This one really through me off, but as it turns out it's pretty much just the same as the normal floats. This one really took me a bit to understand as it really was weird.
Anyways, all that stuff was practically heaven compared to what came next.
Pretty much the worst (almost) three hours of my life
No matter what I did, there is literally nothing I can do to get the STM32 to just initialize its USB port, and just talk to me.
Apparently, I need to set up USB CDC, but the biggest thing is that they really stopped making it easy to use the old libraries, and now you have to use USBX, which I cannot, for the life of me, get to work.
Firstly, it straight up wouldn't find the files. Kept giving me errors. I tried turning off USB and turning it back on, and for whatever mysterious reason it found them now. Well, one down.
Now, you have to turn on and download the USB_DEVICE middleware, but guess what? IT DOESN'T EXIST :(
bruuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuhhhhhhhhhhhhhhh
So now I'm stuck with USBX, something that I initialized, but have absolutely no clue how to use, after several hours spent on it.
Also it doesn't really leave me in the best mood after having spent my entire evening on a piece of code that lowered its ability and added the risk of maybe having destroyed something.
As a side note, I did upload all my stuff to github so there's at least that.

I'm not losing my code again. (Even if it still is quite simple)
4/8/2026 10 PM - Gave GNSS More Time To Set Up
I update my code to read:

This means that if it connects to 10 sats it will signal immediately, and for every 3 seconds that pass it will require one less satelite.
btw I also tested this ofc and everything.
4/9/2026 - Code Improvements, Cleaned up feed so far.
Firstly, I just went into Adafruit IO and just cleaned up all the zero values, but just doing on feed took me so long that I just gave up and deleted the feeds.

All I have now are those 8 points or something.
In addition, I really fixed up my code so that it runs significantly faster, going from ~315ms per cycle to ~63 per with 1/5 being ~113.
In addition, I also fixed it up to catch more errors in 'except' blocks so that the code wouldn't just go and fail.

Two such hot spots

I also updated it to send the battery voltage to Adafruit IO, and well yeah. That's what took me some three hours!
The speed improvements really did take a bunch of time, as I had to move the garbage collector (gc) code so that it was better and only ran once every 2 minutes, in which case it would clear the GNSS UART queue so that nothing weird happened. Finding the gc code was actually quite a thing because I had to run tests isolating everything to see what was actually slowing it all down.
The other 50ms speed savings are by reducing the amounts of write commands called by simply just putting them all to run only once every seconds, because realistically that's all I need, and now when the time goes up everything else will also only update then.
I also played with some boot ordering, so now it will turn on the modem, wait for a gnss lock in which time the modem will (hopefully) connect to the internet, and then transmit.
power_on()
i = 0
while i < 3000:
while gnss.any():
data = gnss.read()
for byte in data:
stat = my_gps.update(chr(byte))
sleep(0.01)
print(my_gps.satellites_in_use)
i += 1
if my_gps.satellites_in_use > (3000-i)//300:
break
connect_to_cell('io')
bat_list = send_at('AT+CBC', show=False).split(',')
send_message(f"{my_gps.latitude[0]}", feed='latitude')
send_message(f"{my_gps.longitude[0]}", feed='longitude')
send_message(f"{my_gps.satellites_in_use}s {round(my_gps.speed[2], 2)}km/h {my_gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
sleep(0.5)
4/10/2026 - Failed To Get STM32 USB Working v2
This time, instead of using USBX, I tried to get the legacy serial to work, and I found this link that has (supposedly) support for the u5 series (link highlights mention)
However, after following their steps word for word, I started getting errors about here:
It was [these lines](https://community.st.com/t5/stm32-mcus/how-to-use-stmicroelectronics-classic-usb-device-middleware-with/ta-p/599274#:~:text=HAL_PCDEx_PMAConfig((,0x140)%3B) that were failing:
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x40);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x80);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, 0xC0);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, 0x100);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD_EP , PCD_SNG_BUF, 0x140);
As I have no clue why this is happening, I consulted GPT. It got me to replace the code with something else as the STM32U5 series uses FIFO or smth for the USB and that has a different setup.
All in all, I got the errors to go away, but I have no clue why it didn't work in the end.
I kept getting:

Anyways, hoping for the best I continued on with following the article.
I got a bit confused over here:
#define USBD_VID 0x0483
#define USBD_PID 22336 /* Replace '0xaaaa' with your device product ID */
But it just turns out it was for nothing. I google what all that stuff means and I think it should be alright, but maybe that's why my USB isn't enumerating...
There were some places where I couldn't understand where the function had to go, but i was able to figure it out after some struggling.
However, after it all did its stuff, for some reason when building the file it just wouldn't work. Huh.
There were some problems, and thus I consulted GPT (a pretty stupid move). It sort of went into hallucination mode and just spit out random stuff that was most likely a bad idea to delete but removed the errors, so I just went along with it.
That still didn't solve the fact that it couldn't find some declarations, but I think that that was because I was running the other usb_desc.c file while it needed a variable in the main.c file or something. However, me not knowing that, I went in with GPT and tried pretty much everything to get it to find the variable, but failed. I don't really remember how I got it fixed or even if the problem was just running the side file.
Anyways, the point is I spent two and a half hours for yet another failed attempt :(
I was actually confident this time ): ): :( :(
Next Steps
Well, I have one last thing that I can try, and that is to get USBX to actually work, as it is the only one that is still natively supported by STM32CUBEMX, and thus I somehow managed to find this video: https://www.youtube.com/watch?v=43gcc2dGnxQ
If this works I guess that would be one heck of a way to go out: struggling to find something so obvious, just like the beginning (with the cell chip, in case (like me) you forgot.
AS I LOG MY 246th HOUR, I must really reflect upon how great of a journey this has been, from a dream some 6 months ago to fruition. (And pretty much everything I designed worked spectacularly!)
4/11/2026 12:08 PM - Made New STM32 Files & Project
So basically, there were a few things bugging me, primarily not being able to undo a lot, the project name, and having all my messed up files from trying the USB things from yesterday.
Thus, to fix them all, I created a CUBEMX copy of the previous project, so it will copy the IO settings, but not the code. I put this all into my GitHub repo so I can then also change my stuff by going back to previous commits.
Lastly, by creating a new project, I naturally got the new name.
All that was left was to copy the correct code over, and now that that's done I should be all good!
Time to TryToGetThisUSBtoWork ยฉ 2026
Btw I also tested the code I put on it, and so far all is well!
4/11/2026 12:26 PM - Configured Clock to be more Accurate
Instead of using the internal HSI clock, I've now re-configured it to use the external 12MHz clock I put on it. This should really help with the time drift that it otherwise experiences of about ~5 seconds per 10 minutes.

4/11/2026 7 PM - GOT USB TO WORK AFTER WHAT FEELS LIKE AGES!!!!!!!!!!!!!!!!!!
You see how I maxed the characters out?
I finally feel ready to submit.
Maybe I'll just add a print function, but otherwise I'm done. I'm actually done.
or i might just work on the i2c and other stuff too...

me using the Arduino IDE serial monitor 'cause I can
Anyways, I'm getting ahead of myself.
The Process.
So I followed that tutorial that I talked about last time, and I actually came to a problem in pretty much the same spot as some other tutorial I did.
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x00 , PCD_SNG_BUF, 0x20);//EP0 OUT
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x80 , PCD_SNG_BUF, 0x60);//EP0 IN
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x81 , PCD_SNG_BUF, 0xA0);//EP1 IN
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x82 , PCD_SNG_BUF, 0xE0);//EP2 IN
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS , 0x03 , PCD_SNG_BUF, 0xF0);//EP3 OUT
However, by some magic, I found the following replacement code and it somehow works!
Not the best, but it still does work. (I'll talk about this later)
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, USBD_MAX_EP0_SIZE / 4);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, USBD_CDCACM_EPIN_FS_MPS / 4);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, USBD_CDCACM_EPINCMD_FS_MPS / 4);
Anyways, I just kept on following the tutorial, even after this hiccup, as I was confident like I was last time (this would be ominous foreshadowing if it didn't work, BUT IT WORKS!!)
But yeah, I just kept following the tutorial, and there were a few places where I got a bit lost, especially near the end, and it didn't help that I can't spell receive(d) or pretty much any word with both an 'i' and 'e' one after the other (there were lots of those)
But yeah, it was a huge let-down when the USB didn't enumerate the first time, but I think there was some little code I messed up somewhere, and after fixing it it just worked.
IT JUST WORKED.
IT JUST WORKED!!
On a side note, I guess it really is true that the more effort you put into something the better it feels when it works...
Testing & 8-Bites
So yeah once everything got to work (~1h40) I then spent the remaining time trying everything in my power to fix the 8-Bite rule.
That, basically, is that the USB was only able to echo 8 characters, one of which a new line.
Awfully inadequate.
If I sent it 9 bites, then it would crash in a (not very) fiery display.
By debugging it, slowly, I was able to isolate the problems. At first, I thought it was the read buffer messing up, but I then, after finding no fix, went in and tried to just make the LED flash once for every bite it received, and believe it or not, the LED flashed at 9 bites.
Well, now that the problem was isolated, certainly a TX messup, I just had to solve it.
If only it were that easy, ofc.
It wasn't. And thus I have no solution. My best bet as of now is just to create a print function that will just make it print 8 bites at a time. Oh well... At least the USB works...
Learning C++ Arrays & springf
So, in the debugging process, I thought that I would make it just print a combination of the length it received and the message. i.e, 3: hi
However, apparently c doesn't have text, and thus I have to add arrays together, again not easy, and something like that. All I know is it works.
Same thing with the springf function. Apparently, its just like printing, except to a char array (the thing we call a string)
Also, look at the difference in complexity between C and Python:
python: response + ' ' + len(response) or f"{response} {len(response)}
c:
a bunch of gibberish no one understands that is ~6 lines long.
Well, time to go get the print function working!
4/11/2026 9 PM - Print Function & Clocks
So I quickly got a basic print function going, and then just vibecoded a more advanced one, so that now you can only send eight bites at a time and not overflow the machinery.
However, as the USB requires really high clocks, at least 28 MHz out of my testing, I set up and played around with the clocks until that they can be set to 0.5 MHz in run mode, and then 48 MHz in USB comms mode.
To test that out, I have my print function checking if the clock is the right speed, and if it isn't then it will increase the clock speed, send all the data, and then lower it back down again.

And here's the data, updating really slowly, just once a seconds, telling me if it is on or not. I'm going to change it to tell me the INA226's readings (that was the whole point anyways) and then I'm going to submit the project!
4/12/2026 5 PM - Got INA226 to work; used it to measure power draws.
Well, the great news is that the INA226 is working, but it is a bit redundant as the cell chip can already take care of basic voltage measuring and functioning as a fuel cell chip.
Anyways, somehow it was as simple as importing the library, and just importing it and using the example code!
There were definitely things I had to tweak, like, y'know the header files to be for the U5, and then there was also the fact that the I2C that got initialized was called hi2ci, and they expected it to be called something else, but all in all it was quite nice.
However, the biggest problems came after I got the library: the library compiled and everything just fine, however it wouldn't output anything. I'd just get zeros. Well, that's not good.
However, for whatever stupid reason, they way I had set it up was to print like this:

And this really triggered my brain.
The thing is, I had forgotten to turn the INA226 on.
Yep. The INA226 draws power from an STM32 IO, and since that was off, well of course it didn't work.
So, I turn it on and ...
It still doesn't work.
For whatever reason, I'm getting 255000 back from it. This is where I realize that I was giving it the wrong I2C handle (after accidentally pasting the code in the GPT) I fix that, ... and, ... nothing.
See, because the return value that the INA226 gives is a float, and my print function can't take floats, I had been converting it into an int. Thus,
My Code Story
My code looked like:
print((int)INA226_getBusV(&some-random-other-address, INA226_ADDRESS));
The thing is, that gives very low resolution, so I made it be
print((int)INA226_getBusV(&some-random-other-address, INA226_ADDRESS)*1000);
This was where I was getting 255000 from btw.
I remove that thousand to get 255
print((int)INA226_getBusV(&some-random-other-address, INA226_ADDRESS));
Then, considering it fixed, I went back and added the *1000 when I update the address.
print((int)INA226_getBusV(&hi2c1, INA226_ADDRESS*1000));
Notice anything wrong?
The *1000 is before the ')' and thus it multiplies the address, not the output.
So, I'm still getting 255s.
Eventually, I figure that out, fix it, and to my absolute shock and surprise, it works!
I'm getting outputs that are around the 3200 range ... but I measure my battery, and it reads ~3999 mV.
Huh, how weird.
Scalers
So the thing is, apparently the library just straight up forgot to put in the scalers, where the resolution for the voltage is *1.25, and the current is *2.5
Fixing those, and finally is all works.
I then spend the rest of my time just cleaning everything up, making it look nice, and just testing it out everywhere!
p.s. An example is the following functions. For a complete update, you can see the github, which should have a commit right about now.
int current_ua() {
return INA226_getShuntV(&hi2c1, INA226_ADDRESS) * 50;
}
int current_ma() {
return (int)(current_ua() / 1000);
}
4/12/2026 6 PM - Tried To Get SD Card to work
I spent a few minutes finding a tutorial to get the SD Card to work, but it really felt like far more trouble than it was worth, with the tutorial videos being like 50 minutes long, plus my own debugging and understanding so I pretty much quit the second they asked for a middleware that I didn't have.

4/12/2026 9 PM - Made SIM7080G Library
Yep. Took me some three hours to debug this thing, but it's there now.
Look at how gorgeous this looks:

Of course, it wouldn't be calle test, but this was just a test so...
Anyways, here are the major hiccups I faced:
Major Hiccups
- New UI, Same Brain
- Weird Ordering
- Thonny Failing
- Secrets
In order:
New UI, Same Brain
The first and biggest thing is that my new thing is much simpler, and just much easier to use. However, my brain is used to the older way of thinking, so that really messes with me.
Now, you just say: self.at("SMSTATE?")
Before you'd have: send_at("AT+SMSTATE?")
I made the at command just .at and now it includes the mandatory AT+. This was something that really messed me up earlier, as I sometimes just typed the command, forgetting the AT+ and expected it to work.
However, now I had the problem that it just wouldn't be happy if I gave it the old one.
In addition, I now had it output the following cases:
(None, None): this is if it times out
(raw response, False): this is when it fails to decode it, but successfully reads.
(decoded response, True): this is the most common (99% of the time) when it successfully decodes the message
However, as I was occasionally lazy and just copied (and both adapted and optimised) a function. The problem was, it expected the answer to be just the answer, not the list. There just were sooo many places where it failed that even after finding every possible place, it still failed and I just gave up and undid it.
In addition, I also made my code to be that there were tonnes of options for the same function, but even then I'd still have a tendency to look back and see what it actually was, which was sort of stupid of me.

Weird Ordering
Here's a snippet of my code for context:
def connected(self):
resp = self.at("SMSTATE?")
if '+SMSTATE: 1' in resp:
return "mqtt"
resp = self.at("SHSTATE?")
if '+SHSTATE: 1' in resp:
resp = self.at("SHCONF?")
return "web"
return None
def connect(self, server, url='', force=False):
if not self.on:
self.power_on()
while "99,99" in self.at("CSQ"): # signal
sleep_ms(100)
mqtt = ['io', 'adafruit', 'mqtt']
web = ['https', "web", "http"]
if not force:
if server in mqtt:
if self.connected() == "mqtt":
return
elif server in web:
if self.connected() == "web":
As you can see, the connected() command requires for the modem to be active and responding, however sometime it isn't until it connects to the cell towers. Thus, you want the CSQ command to run first, as it will just stay there and deal with its problems until it gets a signal.
For some reason, in my previous function it was different as in it would call the connected function first, which would cause the entire thing to hand.
What I find most confusing is that my old code still works, reliably that too, but this one really kept failing some 50% of the time.
The biggest reason this could be is because I really tightened up some timings, removing extra waits and everything.
This really did take me a while for some reason, probably because it was a (mostly) copied part of it that I expected it would work.
Thonny Being A Big Dumb-Dumb-Bell
So yeah, as useful as Thonny is (my really basic IDE that does not support hackatime or any other extensions), there is this big problem that it kept. on. hallucinating.
No matter what I did.
I spent a solid chunk of my time, and thonny kept on saying that a line was calling on the time module, which I didn't import directly. (It was the sleep() function, but I had from time import sleep)
The thing was, that error was exactly correct, just for a problem that was TEN LINES DOWN
I literally questioned my python (and thus coding) skills several times during the debugging.
Last point: Secrets
This is just a feature I added, so that every time I publish to github I'm not giving away my secret information, I just made a secrets file that contains my IO key, my username and the device name.
My complete code.
Because you might be wondering what did I do in these three hours, take a look!
most of this was in fact handwritten, as the previous stuff was all junky-monkey in a weird way that really is shocking still works.
from machine import UART, Pin
from time import sleep_ms, ticks_ms
import secrets
class Cell(object):
def __init__(self, pwrkey=48, pwr_detect=15, init_uart=True):
self.uart_init = False
self.pwrkey = Pin(pwrkey, Pin.OUT)
self.pwrkey.value(0)
self.pwr_detect = Pin(pwr_detect, Pin.IN)
self.init_uart(1)
def warn(self, msg):
print("ERROR:", msg)
def init_uart(self, uart, baudrate=115200, tx=41, rx=42, cts=40, rts=39):
self.uart = UART(uart, baudrate=baudrate, tx=tx, rx=rx, cts=cts, rts=rts)
def cmd(self, cmd):
self.uart.write(cmd+"\r\n")
def at(self, cmd, wait=10, show=False, end="\n", exWait=0, search=False, include="AT+"):
resp = b''
found = False
start = ticks_ms()
self.uart.read()
self.cmd(include+cmd)
while ticks_ms() - start < wait * 1000:
if self.uart.any():
resp += self.uart.read()
if b'OK' in resp or b'ERROR' in resp:
found = True
break
elif search:
if search in resp:
found = True
break
sleep_ms(1)
sleep_ms(int(exWait*1000))
if self.uart.any():
resp += self.uart.read()
if show:
print(">>", cmd)
if resp:
try:
if show:
print("<<", resp.decode(), end=end)
return resp.decode()
except UnicodeError:
if show:
print("<< UNICODE ERROR", end="\t")
print("resp", end=end)
return resp
else:
if show:
print()
return None
@property
def on(self):
if self.pwr_detect.value():
return True
return False
@property
def off(self):
return not self.on
def power_on(self, shush=False, wait_for_boot=True):
if self.on:
if not shush:
self.warn("already on")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
i=0
if wait_for_boot:
while i < 2000:
if self.at("AT", wait=0.1, include='') == "AT\r\r\nOK\r\n":
break
i += 1
def pwr_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def turn_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def boot(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def power_off(self, shush=False, hold=True):
if hold:
sleep_ms(100)
if self.off:
if not shush:
self.warn("already off")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
if hold:
sleep_ms(2000)
def connected(self):
resp = self.at("SMSTATE?")
if '+SMSTATE: 1' in resp:
return "mqtt"
resp = self.at("SHSTATE?")
if '+SHSTATE: 1' in resp:
resp = self.at("SHCONF?")
return "web"
return None
def connect(self, server, url='', force=False):
if not self.on:
self.power_on()
while "99,99" in self.at("CSQ"): # signal
sleep_ms(100)
mqtt = ['io', 'adafruit', 'mqtt']
web = ['https', "web", "http"]
if not force:
if server in mqtt:
if self.connected() == "mqtt":
return
elif server in web:
if self.connected() == "web":
return
else:
self.warn("huh?")
self.at('SMDISC') #DISCONNECT FROM ADAFRUIT IO
self.at('SHDISC') #DISCONNECT FROM HTTP
self.at('CGDCONT=1,"IP","simbase"')
self.at("CEREG?", show=True) #signal type
while "NO SERVICE" in self.at("CPSI?"):
self.at("CSQ")
self.at("CEREG?")
sleep_ms(100)
#self.at("AT+CGACT?") #active?
#self.at('AT+CGDCONT?') #ip & stuff
self.at('CGPADDR=1') #connect to wifi
self.at('CNACT=0,1', exWait=0.1)
self.at('CACID=0')
if server in mqtt:
self.at('SMCONF="URL","io.adafruit.com",1883')
self.at(f'SMCONF="CLIENTID","{secrets.client_id}"')
self.at(f'SMCONF="USERNAME","{secrets.user_name}"')
self.at(f'SMCONF="PASSWORD","{secrets.key}"')
self.at('SMCONN', 10)
if server in web:
self.at('SHCONF="BODYLEN",1024')
self.at('SHCONF="HEADERLEN",350')
self.at(f'SHCONF="URL","{url}"')
self.at('SHCONN')
def send_message(self, msg, feed="test"):
self.connect('io')
self.at(f'SMPUB="space_coder/feeds/{feed}",{len(str(msg))},1,0', end=" ", search=">")
self.uart.write(str(msg))
start = ticks_ms()
resp = b''
found = False
while ticks_ms() - start < 5000:
sleep_ms(1)
if self.uart.any():
resp += self.uart.read()
if b'OK' in resp or b'ERROR' in resp:
found = True
break
try:
print(resp.decode())
except Exception as e:
print(e)
print(resp)
4/13/2026 8 PM - Re-organised Files, Worked on a More Intuitive On/Off
Basically, I just, one at a time, copied the files and put them into new folders so that they are actually nice!
Here's what it looks like as of now.

One small quirk is that I have to import tft.font1 now as its in the tft folder.
Then, I worked on creating a better way to turn on/off the sim7080g, and here's what I came up with:
Basically, like last time, I'm still gonna have lots of alias functions, but now the main ones is just .on
The thing is, this created the need for me to free up the previous .on/.off functions, and now they are named .ison/.isoff, which required me to update every single reference.
Other than that, I think all is well now...
Code snippets:
@property
def is_on(self):
if self.pwr_detect.value():
return True
return False
@property
def is_off(self):
return not self.is_on
def power_on(self, shush=False, wait_for_boot=True):
if self.is_on:
if not shush:
self.warn("already on")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
i=0
if wait_for_boot:
while i < 2000:
if self.at("AT", wait=0.1, include='') == "AT\r\r\nOK\r\n":
break
i += 1
def pwr_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def turn_on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def boot(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def on(self, shush=False, wait_for_boot=True):
return self.turn_on(shush, wait_for_boot)
def off(self, shush=False, hold=True):
if hold:
sleep_ms(100)
if self.is_off:
if not shush:
self.warn("already off")
return
self.pwrkey.value(1)
sleep_ms(1300)
self.pwrkey.value(0)
if hold:
sleep_ms(2000)
def turn_off(self, shush=False, hold=True):
self.off(shush=False, hold=True)
def power_off(self, shush=False, hold=True):
self.off(shush=False, hold=True)
def shut_down(self, shush=False, hold=True):
self.off(shush=False, hold=True)
Btw I did some debugging for a future journal, and oh boy does the simpler version come in handy without the 'AT+'. (Such as cell.at("SMDISK"))
4/13/2026 10 PM - New Tracking Code +
Yep, I got this sweet new tracking code that is soooooo much simpler and as I'm redoing everything much easier to use and see.
My code
is as follows:
# Important Imports
from machine import Pin, UART
# Prevent Automatic Shutdown / Comms
stm_com = Pin(43, Pin.OUT)
stm_com.off()
# Imports
from sim7080g import Cell
from gps import GPS
from time import sleep_ms
# Check if boot was for tracking or for
track_pin = Pin(9, Pin.IN)
TRACK = True# if track_pin.value() else False
# Simpler swap-ins to help simplify typing
ms = "ms"
# initialize objects
cell = Cell()
gps = GPS()
# Function Defines
def shut_down():
stm_com.on()
def track():
i = 0
cell.connect('io')
while i < 20_00:
if gps.sats[0] > 5:
break
sleep_ms(10)
i += 1
print(gps.sats)
while i < 40_00:
if gps.lock:
break
sleep_ms(10)
i += 1
if i < 40_00:
bat_list = cell.at('CBC').split(',')
cell.send_message(f"{gps.pos[1][0]}", feed='latitude')
cell.send_message(f"{gps.pos[0][0]}", feed='longitude')
cell.send_message(f"{gps.sats[0]}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) wait: {i//100}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {my_gps.satellites_in_use}s {my_gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
cell.off()
# Main
def main():
if TRACK:
track()
return
'''cell.send_message("i'm awake!")
cell.at("SMDISK")
cell.off()'''
while True:
gps.update()
if gps.new:
print(gps.time, gps.pos, gps.spd, gps.sats)
if __name__ == '__main__':
main()
Basically, I have this sick new code that should be waaaay easier as the main function will just run the tracking code if it detects that it needs to track itself, and otherwise just run normally.
If you are looking, you might see that newer, fancier gps code, as I'm addressing it as gps. But then you might look at this line and wonder: whaaaat?? The line in question: gps.gps.hdop
Why the funkyness?
Because, I developed
A New GPS Library
Ok, not quite. What I basically did was just create an inner layer that will basically take in commands, and then output them in a nice way, while giving me control to change something at every step of the way.
As its setup, here's my gps.py code:
from micropyGPS import MicropyGPS
from machine import UART
from time import sleep_ms
class GPS(object):
def __init__(self, uart_id=2, rx=5, tx=4, tmzone=-4):
self.uart = UART(uart_id, baudrate=9600, rx=rx, tx=tx)
self.gps = MicropyGPS(tmzone, 'dd')
self.cmd("$PMTK251,115200*1F")
sleep_ms(100)
self.uart = UART(uart_id, baudrate=115200, rx=rx, tx=tx)
self.cmd("$PMTK220,200*2C")
self.last_given_timestamp = []
def cmd(self, msg):
self.uart.write(f"{msg}\r\n")
def update(self):
if self.uart.any():
data = self.uart.read()
for char in data:
self.gps.update(chr(char))
@property
def time(self):
self.rq()
return self.gps.timestamp
@property
def pos(self):
self.rq()
return [self.gps.longitude, self.gps.latitude]
@property
def position(self):
return self.pos
@property
def speed(self):
self.rq()
return round(self.gps.speed[2], 2)
@property
def spd(self):
return self.speed
def rq(self):
self.update()
self.last_given_timestamp = self.gps.timestamp
@property
def new(self):
return self.gps.timestamp != self.last_given_timestamp
@property
def sats(self):
self.rq()
return [self.gps.satellites_in_use, self.gps.satellites_in_view]
@property
def satellites(self):
return self.sats
def use_sats(self):
return self.gps.satellites_in_use
def view_sats(self):
return self.gps.satellites_in_view
@property
def lock(self):
long_lock = self.position[0] != 0
lat_lock = self.position[1] != 0
return True if lat_lock and long_lock else False
Please note that every single character there was typed by hand, save a few copied @propertys and like one function that was copied.
Again, like with my last library, you will notice a few aliases, and again this is just so that stuff can be typed a lot easier. Key functions this has that the other one didn't include:
- Organised Position (returns a list, longitude then latitude)
-
satsfunction to tell you both the visible and connected satellites -
lockfunction to tell you if it has actual coordinates detected -
newfunction to tell you whether or not you have accessed anything from this timestamp -
rqfunction to notify the library that you are using data, only necessary when you are directly using the lower-level functions of the micropygps library. All implemented functions and read requests automatically call it.
You will also notice that there are some nice, juicy, comments!
This way any person who comes along later to take a look at my code will understand all that is going on here.
I also published all my ESP32 firmware to github!
4/14/2026 11 AM - Fixed Old Code to Work Better
Basically, I adjusted the timing so that the esp32 would boot up and, as I'm in a big conctrete structure, if it doesn't see enough satelites it will just send battery data and tell me how many sats it sees (if any)
I pretty much just adjusted some timing on the ESP32 side of things so that it wouldn't get reset by the STM32.
Since I was using viper-ide.org, a far messier and slower IDE, it took forever to actually connect to the ESP32 as it had some bugs in the connection phase.
Definitely didn't help the the ESP32 was getting reset every few seconds.




I finally got some real photos!
4/14/2026 8:18 PM - GUI
So, I want to have a nice little GUI, so I was thinking of using LVGL, as @sasha recommended.
The problem is, however, that I have to get it to work by building micropython for it, and after doing some research and stuff it seems possible, but it will be a pain to get both the LCD driver stuff and the LVGL stuff into the micropython file and actually build it and all that stuff, so I'm thinking of just going with the current setup and just rewriting the main file so far.

4/14/2026 8:22 PM - Taking my tracker for a trip (1/2 hours counted)
So, to really test my tracker out, I took it out for a trip on the train!
Here's what it looks like, the big straight line being a place without good gps signal.


one is longitude, the other is latitude.
4/14/2026 9 PM - Finalized Tracking Code; Started Normal Run Code
Yep, I just did that.
I just updated the message statements to be:
if gps.lock:
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {ticks_ms()-st//1000}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
Basically, it now also shows the satellites in view, and separates them by a '/'
I also made the entire thing time based so that it will not be unpredictable, and it will always take a max of 30 secs for the first part, 10 more for the second.
I also put all that in a proper try/except block.
In addition, I also programmed the STM32 to now give it more slack, with 70 seconds to update, and updates once every 5 minutes. Should give me better data for now.
def track():
try:
st = ticks_ms()
cell.connect('io')
while ticks_ms() < 30_000 + st:
if gps.sats[0] > 5:
break
print(gps.sats)
while ticks_ms() < 40_000 + st:
if gps.lock:
break
bat_list = cell.at('CBC').split(',')
if gps.lock:
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {ticks_ms()-st//1000}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
except Exception as e:
print(e)
shut_down()
except KeyboardInterrupt:
return
shut_down()
uhhh what should i put for a picture...
well my battery is dying...

Time to swap it out.
4/14/2026 10 PM - TFT Stuff
I spent the last half-hour just tuning and finding a good color for the foreground and background, and I settled on 0xEE55 (rgb565) for the front and rgb(8,0,8) for the back.
I also worked on the tft implementation, and here's the code!
import st7789
import tft_config
import tft.tfont2 as big_font
import tft.tfont1 as speed_font
import vga2_bold_16x32 as font
import vga1_8x16 as small_font
class TFT(object):
def __init__(self):
self.right = tft_config.config1(2)
self.left = tft_config.config(2)
self.right.init()
self.left.init()
self.speed_font = speed_font
self.big_font = big_font
self.font = font
self.small_font = small_font
self.st7789 = st7789
def fill_all(self, color=0):
self.left.fill(color)
self.right.fill(color)
@property
def black(self):
return st7789.BLACK
@property
def white(self):
return 0xEE55
@property
def sky(self):
return 0x867E
Well yeah, that sure did take a long time for the color, but it looks quite nice, it's a deep violet that is pretty hard to notice, but just way more soothing than plain black, and the foreground is a slight yellow, that just looks so nice.
I really also had to mess with the code as its name was tft, and it conflicted with something else and just failed. Then there was the problem of the entire library itself, but it just didn't sit right and I might just fully scrap it. Hmmmmmm....

the light yellow ^^
4/15/2026 10:04 PM - Cooked Up Micropython, Font Errors, Implementation
Ok, so first thing's first, I decided that I want micropython. Custom micropython. Thankfully for me, my driver's repo tells me exactly how to compile it.
Just don't worry about the fact that it is some 4 years old or smth and half the stuff doesn't work. The first thing we have to think about the fact that making the ESP-IDF branch isn't easy more. Thankfully, there is only like one more step.
But, before getting to any of that, I had to actually install WSL, or something, which allows me to run linux stuff on windows. This was a pain, as I wanted to set the password to a blank one, but for some reason it didn't show, and instead I had to then redo that, which required uninstalling, and for some reason the uninstall options just didn't show up in the apps folder.

Weird, eh? There should be the app that would then let me uninstall it.
Well, after figuring it out, it still didn't help with me being absolutely unable to build micropython.
The amount of times i've had to run:

Like its actually depressing. This entire thing has taken me over 6 hours (including some UI and pythoning) and its just absolutely killing me.
I once got it to build properly, but for whatever reason it just didn't enumerate and gave me a weird error saying something about the I2C failing and rebooting.
I don't really know what to say: I've tried both using the right version of IDF with the latest Micropython, but that's not compatible with the st7789 driver, and I've tried the older version of micropython but then ESP-IDF keeps failing. If you want to see all my stuff, take a look at the following chat-gpt conversations.
https://chatgpt.com/share/69e02d43-fab0-83ea-9b27-8630b958d6e7
https://chatgpt.com/share/69e02d6d-0760-83ea-87d5-00eaa9fc94e4
https://chatgpt.com/share/69e02d82-9840-83ea-be03-a4d5e1230b30
https://chatgpt.com/c/69dfeba4-845c-83ea-ab5d-1049f9765772
Reflashing Mishaps
So remember how I said that I messed up the compilation and it messed it up and stuff? Well thankfully, I had taken a back up of my files, but it lost all its organization, and thus gave me so much trouble.
But, before I could even get my good micropython on, I ran into the time-old problem of flashing the wrong file.
The thing is, my ESP32s3 has octals-psram, not normal spiram. However, I had taken the firmware file of the spiram one and I was wondering why it wasn't booting. Finally, in a mostly panicked state, I got the firmware from the official side and somehow that worked. I then went on to putting all the files back, and completely losing them later. Basically, I added the official firmware, got it to boot, and then promptly proceeded to re-flash the broken spiram file and pretty much going crazy.
uggggggggggggggggggggggghhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
I really wanted it to work before I submitted it :(
Current Micropython Building
Ok, so just leaving that all out, I'm going to try building the micropython as the LVGL directory says, and see what happens...
I found a driver for the LVGL and lucky me, even though I can't find the spot to put the driver files, after a bit of exploring it looks like they are already there!

Time to build...
Huh, they have my ESP32-S3 there!
Time to install esp-idf for the bajillionth time!
IT WORKED!!!!!
JUST FOLLOWED IT AND IT JUST WORKED

(this alone took me over an hour btw (it was at the same time as some miner ui tweaks))
getting the usb to find and flash was a bit of a hassle, but it actually worked!!!
(I unflashed it now to use my old code again though)
UI and Other Micropython Stuff
So, I spent a good chunk of time trying to display text. If you recall, some time ago I made custom fonts so that I can have really big text.
Well, guess what!
I completely forgot that I have a very limited character set. How limited? Just the following characters:
'K' 'M' '/' 'H'
So, when I try to put, for example, 'hi', nothing shows up.
At first, I thought it was a screen thing. Maybe I'd not initialized the screen properly or the coordinates at which it was displaying were wrong. No worries, I tried putting the km/h text from underneath the number to the other screen. And for some god-forsaken reason it worked. The km/h displayed. I was like wth the code is the literal same, why isn't it showing anything???
Debugging: maybe it needed to have a slash? So I try the string 'h/i' and, as you, an all knowing overlord, would know, it worked. But only the '/'
I was full on going weird alarms, but then my not-very-good-but-sometimes-useful ai assistant helped me out and told me to look at the font.
And here lied the problem (as I already said)

Well yeah, quick fix just change the font to one that actually had full support.
I then programmed it to have special stuff so that it would only update the screen if the speed actually changed, and at first I just had a function on the inside, that would just check if the last speed value given was different than the current one and return True if that were the case.
Just one small problem, at the very beginning of the loop I was requesting the .spd attribute, so there was never a change from the last call (at the beginning of the loop) and the display call.
I moved the print down, but as it just didn't sit right I changed the code to the following:
while True:
gps.update()
if gps.new:
if old_speed != round(gps.spd):
tft.left.write(
tft.speed_font,
f"{round(gps.spd):02d}",
10, 30,
tft.white,
tft.black
)
print("spd")
old_speed = round(gps.spd)
tft.right.text(
tft.font,
str(gps.spd)+" ",
10, 30,
tft.white,
tft.black
)
print(gps.time, gps.pos, gps.spd, gps.sats)
I also added speed delta functions that would tell you the difference between the last speed request and the current speed.
@property
def delta_speed(self):
last = self.last_speed
return self.speed-last
@property
def delta_spd(self):
return self.delta_speed
They aren't being used as of now.
I also fine tuned where the decimal speed is now. (And of course added it in the first place) It still does feel weird though.
NOTE
y'know it is kinda sad that I'm going to be submitting today, and well yeah uh I would absolutely love to continue to work on this, maybe for some other ysws or just on my free time. I'm thinking that I will just continue to journal my time over on a google doc and you (the reviewer) can chose to add those hours. Please give me a note if you do so that I don't double dip. I really do want to finish with it working tho... GOOGLE DOC: https://docs.google.com/document/d/1c-lNGmR0YAwbqmByu39EthkbhZPvO9BD816Yln8PGi8/edit?usp=sharing
4/15/2026 10:20 PM - testing 123
ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss i can journal???????????

4/17/2026 - Spent an Hour Looking At LVGL & STM32 USB & Tracking
Y'know, the limit for the characters in the title really is pretty sad...
Anyways, I spent like an hour trying to understand LVGL and see if my screen is supported, and the good news is that it is.
The bad news, however, is that I can't find a single doc online showing me how I use it or any other built in driver included with the library.
All the docs are for the C applications.
Anyways, next! (I know that I'm just skimming over it, but there really isn't much more to say other than I just spent an hour looking for some way to get it to work. I might get it to work, but still not easy)
C
I lowk am just thinking that I might just re-write my code to run with the arduino IDE as it should actually work and that way I can use the display with LVGL. I really don't want to rewrite my code again (not quite done, but almost for my 2nd iteration)
STM32 USB
For some reason, my STM32 is drawing a tonne of current, about 5mA, and I really need to get that down. Currently, it lasts for about 5 days or so per battery for just tracking, as the average power draw is 20mA, 5mA + 160 mA for ~25 seconds multiplied by 12 times an hour.
I might try to get the ESP32 to go on a diet and shut up a bit so that should save power, but I tried to get the USB to save power, and that was pretty much useless. I might try again later, but as it is right now the vast majority of the power is being drawn by other peripherals and stuff, so that's where I gotta save power. The biggest thing I see is the USB connection, but that might be a tough thing as I have to de-initialize it, and then re-initialize it and it just hangs there for some reason. It's really weird but it just doesn't want to follow my (chatGPT) commands.
GPT is probably just hallucinating, y'know.
STM32 Cube programmer also just hung for whatever reason:

Yeah, I think I also took care of tracking, but I also just worked on making it actually work so that its seeing if the long/latitude is equal to 0, not the list.
long_lock = self.position[0][0] != 0
lat_lock = self.position[1][0] != 0
I also fixed the issue that in the time that it took to transmit it would lose the lock or something occasionally, so I have added two additional functions banning the library from auto-updating whenever it is called, and then unbanning it.
def ban_updates(self):
self.allow_update=False
def allow_updates(self):
self.allow_update=True
and implementation:
def rq(self):
if self.allow_update:
self.update()
self.last_given_timestamp = self.gps.timestamp
NOTE: HOURS DECREASED BY 0.5 AS I SORT OF GOOFED UP ON THE PREVIOUS JOURNAL

4/18/2026 8 PM - Worked On Power Saving
I did a bunch of experimentation, and here are the numbers for idling.
The ESP32 draws about 45mA, out of which 5mA can be saved by lowering the clock speed. (160MHz to 80MHz)
The GPS draws about 20~25mA which can almost fully be eliminated by putting it to sleep, really useful for the tracking, where after getting a lock we can turn it off.
the SIM7080G draws 35 to 45 mA idling, and literally no matter what I do it won't go down. I tried turning of the radio and stuff, but that's not of much use (just experimentally) and that saved about 5mA. I tried to turn on PSM (Power Save Mode) but it just wouldn't enable for whatever reason.
And that's pretty much my entire gain after spending two hours on it. Yipeeeeeeee.
I really did spend a long time researching about the power modes of the SIM7080G, as it draws some 80~100mA during transmission.
I also spent a bunch of time reading through the STM32U5 power optimization guide, and pretty much nothing out of it was really of much use.

I'm still shocked that its drawing ~4.6mA just sitting there.
Main Code
I also worked on developing the main code, so much so that I think it's all set, and I'm now just making the main file import the new code and run it.
I'm now going to try and get asyncio to work so that the SIM7080G only turns on when needed, so I can save the huge power draw!
4/18/2026 10 PM - Worked on Getting Battery % and Voltage
So, I got it to work but it has a really weird jiggy thing in the beginning where it just takes a bunch of time.

The middle row is time taken to complete in milliseconds, and for some reason it spikes, but only in the very beginning. It doesn't happen anywhere else at all. I've tried debugging everything, and I just have no clue whatsoever what is going on. I'm just gonna have to accept it, I guess...
Here's what I've tried in terms of checking if there is a blocking function
- Checking asyncio is working properly
- Checking if its the LCDs
- Checking if the Cell library is blocking
And I still have no clue.
The good news is, however, that I got asyncio to work!
Now, every 45 seconds, the SIM7080G will wake up, check the battery percentage, and shut back down.
I just have to speedrun add the data to the screens later and it will be well!
I also added the timestamps working as in it will update it once every time the GPS updates (200ms) and 100ms after each update.
This way the time will never stray and it will appear like its updating every 0.1s.
Also, thanks to the way that the SIM7080g only wakes for a few seconds, the majority of the time when the shutdown is requested it can be fulfilled so fast that the STM32 just turns it back on again, thus I added a 500ms delay. It actually makes it feel way more natural.
Also, my code has so many comments that it doesn't feel like my own :)
Really nice to know what everything is doing, even if they are a bit over the top sometimes.
Here's my entire main.py code. For the other libraries you can check out the github, which is now updated quite frequently.
# Important Imports
from machine import Pin, UART, freq
# Prevent Automatic Shutdown / Comms
stm_com = Pin(43, Pin.OUT)
stm_com.off()
# Imports
from sim7080g import Cell
from gps import GPS
from time import sleep_ms, ticks_ms
import gc
import uasyncio as asyncio
# Check if boot was for tracking or for
track_pin = Pin(9, Pin.IN)
TRACK = True if track_pin.value() else False
# If running normally, import the screen driver
if not TRACK:
from screen import TFT
# Simpler swap-ins to help simplify typing
ms = "ms"
# initialize objects
cell = Cell()
gps = GPS()
# initialize IOs
shut_off_pin = Pin(0, Pin.IN, Pin.PULL_UP)
shut_off_pin2= Pin(44,Pin.IN, Pin.PULL_DOWN)
# initialize slow objects if not tracking
if not TRACK:
tft = TFT()
# Function Defines
# To shut everything down
def shut_down():
if cell.is_off:
sleep_ms(500)
else:
cell.off()
stm_com.on()
def track():
try:
freq(80_000_000)
st = ticks_ms()
while ticks_ms() < 15_000 + st:
if gps.sats[0] > 5:
break
print(gps.sats)
cell.connect('io')
while ticks_ms() < 40_000 + st:
if gps.lock:
break
bat_list = cell.at('CBC').split(',')
if gps.lock:
gps.off()
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV)", feed='other-info')
except Exception as e:
print(e)
shut_down()
except KeyboardInterrupt:
return
shut_down()
# Main
async def main():
# Track if requested
if TRACK:
track()
return
# Set up screens
tft.fill_all(tft.black)
tft.left.write(
tft.big_font,
b'KM/H',
10, 200,
tft.white,
tft.black)
# Starting Variables
old_speed = 978
last_gc_collect = 0
last_cell_on = 0
btry = None
sat_string = ''
# Clear GPS Buffer
gps.uart.read()
# Run loop
while True:
# Update GPS Values
gps.update()
# await to give the cell on/off functions a chance
await asyncio.sleep_ms(5)
# check if cell chip is on
if cell.is_on and btry==None:
btry = cell.btry
print(btry)
asyncio.create_task(cell.aoff())
if last_cell_on + 45_000 < ticks_ms():
asyncio.create_task(cell.aon())
btry = None
last_cell_on = ticks_ms()
# Check if shutdown signal is given
if not shut_off_pin.value() or shut_off_pin2.value():
shut_down()
# Collect GC if it is getting full
if ticks_ms() > last_gc_collect + 120_000:
print("COLLECTING GARBAGE")
gc.collect()
last_gc_collect = ticks_ms()
# Update screens if there is new GPS information
if gps.new:
# Don't let GPS values change while updating screens
gps.ban_updates()
# Get starting time to monitor time taken to update
s_t = ticks_ms()
# Add main speed
if round(old_speed) != round(gps.spd):
tft.left.write(
tft.speed_font,
f"{round(gps.spd):02d}",
5, 30,
tft.white,
tft.black
)
# Add screen decimal and hdop
if old_speed != gps.spd:
tft.left.text(
tft.font,
f"{round(gps.spd%1, 1)}"[2:],
210, 160,
tft.white,
tft.black
)
old_speed = round(gps.spd, 1)
time_stamp = ':'.join(map(str, gps.time))
tft.right.text(
tft.font,
time_stamp,
10, 10,
tft.white,
tft.black
)
# Re-allow updates
gps.allow_updates()
if sat_string != f"{gps.sats[0]}/{gps.sats[1]}/{gps.hdop:0>.1f}":
tft.right.text(
tft.font,
f"{gps.sats[0]}/{gps.sats[1]}/{gps.hdop:0>.1f}",
10, 48,
tft.sky,
tft.black,
)
sat_string = f"{gps.sats[0]}/{gps.sats[1]}/{gps.hdop:0>.1f}"
# Print info out
print(gps.time, gps.pos, gps.spd, gps.sats, gps.hdop, ticks_ms()-s_t, last_cell_on + 45_000 - ticks_ms())
if s_t + 100 < ticks_ms():
time_stamp = str(gps.time[0]) + ":" + str(gps.time[1]) + ":"
time_stamp += str(gps.time[2]+0.1)
tft.right.text(
tft.font,
time_stamp,
10, 10,
tft.white,
tft.black
)
s_t = ticks_ms() + 200
if __name__ == '__main__':
asyncio.run(main())
btw, debugging the super long blip in the beginning was quite painful, and I really tried to optimise the code to run as quickly as possible to get the 10hz update, but it didn't work out. The python library is just too slow.
I did increase the buffer size to 4kb though, if that might help...
Also, here are the async definitions incase you wanted to see.
async def aon(self, shush=False, wait_for_boot=False):
if self.is_on:
if not shush:
self.warn("already on")
return
self.pwrkey.value(1)
await asyncio.sleep_ms(1300)
self.pwrkey.value(0)
i=0
if wait_for_boot:
while i < 500:
if self.at("AT", wait=0.03, include='') == "AT\r\r\nOK\r\n":
break
await asyncio.sleep_ms(70)
i += 1
async def aoff(self, shush=False, hold=False):
if self.is_off:
if not shush:
self.warn("already off")
return
if hold:
sleep_ms(100)
self.pwrkey.value(1)
await asyncio.sleep_ms(1500)
self.pwrkey.value(0)
if hold:
sleep_ms(2000)
I might also add the AT one as it really would be nice to async it...
4/19/2026 - Fixed Glitch, Async'd Code, MQTT and Safe Power, New Fonts.
Gods, the 60 char title limit is annoying.
Anyways, I spent even more time debugging the glitch (where the code just randomly halts in the beginning for whatever reason) and now I have serious reason to believe it's the GPS which is just sending a huge packet of info, which slows the parsing.
To fix this, I've added a huge buffer, which holds 4kb of data, and made the update calls more controlled. Now, only where it's acceptable, it will take a look at the first 128b of data, and then if it still has time it will keep looking at the next 128. This way when we get a huge dump of like 4kB or something, it won't just hang the loop for like a second or two.
Next, I really added a bunch of async-ing, as basically all that the AT commands are is just send message in <1ms, wait 10~30~3000 ms, and then read.
It's actually lowkey so cool how I can just have two functions that for my purpose run completely at the same time.
I have the main function call the update_mqtt() function, and this then runs pretty much at the same time and just updates the web server.


Basically, all I do is that I just tell it to run, and every time the main cycle awaits, then the connecttoio function works and it just runs the next command.
It would be faster to run it on the other core, but that's just going to add too much work and make it impossible to debug and stuff. (Speaking from experience)
Of course, like with all programming this wasn't easy or anything. Firstly, like last time, I really had to pinpoint the cause of the slow down, and once I had the solution wasn't apparent immediately. I really didn't understand that it would just work so well, and thus I tried looking for pretty much any other solution. I was really thinking of multi-threading it.
There was also this place where I didn't know that you can await midline, like here:
while "99,99" in await self.aat("CSQ"): # signal
This really did cause a hiccup, with it hanging for several seconds here sometimes.
Before I made my code all nice and tidy as it is right now, there were several issues with it, such as it firing off commands in different orders because of the variable orders and stuff.
I really had a mess with all the power on, battery fetching, and shut off codes all asynchronous, which could end up with them being fired independently. In the end, I decided to go for the solution with just having a seperate function that would just call and run the commands in order.
Awaiting
Getting that to work was also quite something as I may have mentioned before, but everything sort of went quite well other than the hiccup above.
It did require some messing with, and in the end I made just three asynchronous functions, aat() for at commands amsg() to send messages to the AT server and aconnect() to connect asynchronously.
aconnect:
async def aconnect(self, server, url='', force=False, show=True):
if not self.is_on:
await self.aon()
while "99,99" in await self.aat("CSQ"): # signal
await asyncio.sleep_ms(100)
mqtt = ['io', 'adafruit', 'mqtt']
web = ['https', "web", "http"]
if not force:
if server in mqtt:
if self.connected() == "mqtt":
return
elif server in web:
if self.connected() == "web":
return
else:
self.warn("huh?")
await self.aat('SMDISC') #DISCONNECT FROM ADAFRUIT IO
await self.aat('SHDISC') #DISCONNECT FROM HTTP
await self.aat('CGDCONT=1,"IP","simbase"')
if show:
await self.aat("CEREG?", show=True) #signal type
while "NO SERVICE" in self.at("CPSI?"):
'''self.at("CSQ")
self.at("CEREG?")'''
await asyncio.sleep_ms(100)
#self.at("AT+CGACT?") #active?
#self.at('AT+CGDCONT?') #ip & stuff
await self.aat('CGPADDR=1') #connect to wifi
await self.aat('CNACT=0,1', exWait=0.1)
await self.aat('CACID=0')
if server in mqtt:
await self.aat('SMCONF="URL","io.adafruit.com",1883')
await self.aat(f'SMCONF="CLIENTID","{secrets.client_id}"')
await self.aat(f'SMCONF="USERNAME","{secrets.user_name}"')
await self.aat(f'SMCONF="PASSWORD","{secrets.key}"')
await self.aat('SMCONN', 15)
if server in web:
await self.aat('SHCONF="BODYLEN",1024')
await self.aat('SHCONF="HEADERLEN",350')
await self.aat(f'SHCONF="URL","{url}"')
await self.aat('SHCONN')
if show:
print(self.is_connected, "connected, iirc")
Data Use
Since I've updated it to update the server its started to draw and alarming 0.5mb per day, which brings the per day cost to around 3.5ยข. Per year, that's about 10 dollars. Ideally, I'm going to find a way to really lower that...
Fonts
I also took a look at the script again and generated some new fonts so that everything would be far more legible.
I also made the battery percentage be green above 30% and red below.
Auto Shutdown
For when the battery is dying


Basically, if the INA226 detects battery voltage below 3200 mV, then the STM32 will force shutdown the ESP32 by turning it off if it is updating, then turning it back on in while telling it to turn off. This will force it to turn off the SIM7080g, and then it will tell the STM32 to turn it off.
Then, the STM32 will itself go into shutdown mode, which should draw less than a ยตA.
I can confirm, that it does work. I speedrun discharged one of my batteries while journaling so that I could test it out.
Y'know, the biggest reason I decided to actually do this was because I've now over discharged the same liion twice.
4/21/2026 - Got TOUCH TO DETECT!!!! &tracking
Here's the furthest I got to:

You can see that the stm32 is detecting that the interrupt pin is being pressed, so you know for sure that there is some activity going on!
There were just so many problems: this was almost as hard as getting the USB to work
Like that hard.
It firstly took so long for my to try to make a claude account because apparently its better as I was determined to vibecode it.
No good. I don't have a phone number. Fall back, gemini. It was lowk pretty useful, it got me through the debugging alright, I guess.
The biggest thing was that I had to:
- Use the right I2C bus
- JUST SCAN THE GOSH DARNED BUS
Yep, it was just a library problem. My chip and everything were working, it's probably something in the vibe-translated library.
Basically, we (yes, I count gemini as a person) went through the debugging process, first scanning the bus, seeing the wrong values, ensuring the thing was connected (it was) ensuring all was like working, I started to think I forgot the I2C pull-ups (I didn't) and finally I though that maybe I'll just paste my code, and remind it that I'm using hi2c2 not hi2c1. And that fixed it: the code it gave me assumed it was. Quick fix, and now I get a bajillion addresses.
I guess that's better than none!
So why is that? I guess that's because of the timing, because when I gave it a Gemini Generated Timing, it worked and detected! Now I was only getting the one address I wanted, and everything seemed to go swimmingly, except, and this is about where I am, the fact that it wouldn't connect.
I've tried bypassing the connection-checking software, and still nothing. All I can do now is hope that tomorrow fixes it!

As you saw though, I can at least use the interrupt pin to tell me when a touch occurs, and that might just be something. I could then tell the ESP32 that and we'd be balling!
Again, to not bore you and for the interest of time (and my forgetting-ness) I haven't quite been very deep on the points, but I assure you this is the gist!
4/22/2026 - Fixed GPS Tracking issue.
As far as I can tell, for the past some ~30 hours I've been running it, and not once has it given me a '0' and so I can now be confident that its working properly!!!
It sure is nice to have finally fixed the problem.
Latest thing I changed is that I allowed it to read the entire buffer in the tracking loop, and that I've banned updating as it starts to send data.
Previously, I'd sometimes have a '0' in one but not the other, and this seems to have fixed it!

4/24/2026 2 PM - Spent ~2 ยฝ hours debugging a 13ms delay.
So I had this really big problem that my loop was taking 13ms longer than I wanted to and it literally made negative sense.
I had print statements in every single thing that would run to tell me if it ran, and I even double triple checked the USB was working properly and I ended up just going through each and every little nook and tried to isolate the problem.
I was so confident that the main loop wasn't the problem as everything was accompanied by a print statement. I thought it was more likely for the USB to fail.
As it turns out, it was something in the main loop. It was in an if statement, and that's why I never suspected it until I started commenting out each block of code one by one.
So here's the culprit: CST328_IsTouched(&touch)
The thing is I had left off at this spot last time and hadn't got it to work so I was wondering why the interrupt handler was not working anymore. At first I thought I broke the cable (screen to board) but I ruled that out as once in a while it would detect the touch.
So (GPT was certain the the USB was failing)[https://chatgpt.com/share/69ebafb7-92b4-83ea-baad-32d8d47ba9c2] but I ruled that out by determining how long the write took, less than once ms, but GPT straight up wouldn't believe me so it kept saying the same thing. It really lowk is just stupid.
The other thing that GPT suggested was that it could have been a clock issue that it wasn't initializing properly, but I was able to rule that out by seeing that the time added wasn't constant, so when it should have took 300ms it took 312, 500 to 520, and 1000 took 1014.
If it were to have been a clock issue it would have been constant.
Eventually I just thought that screw this I might as well try turning everything in the loop except for this read funcion off, and it just worked somehow!
From there it was quite straight forward, I just checked each piece!

^^ It's working!! ^^
Next up, I'd like to get the position from I2C, as just like last time all I can do is just tell if a screen has been touched by the interrupt pin going down.
4/24/2026 4 PM - Got Touch To Work!
Yep, now the STM32 is able to detect the touch!
It took what felt like forever to work, even though it was only an hour and a bit, because of the fact that c is just so slow.
Not in terms of running โ it runs faster than python by like 30x or something iirc, but in terms of compilation, uploading, and then debugging.
I probably had to go through over 30 iterations, each one taking forever.
In addition, in the beginning I had it set that you can't even use it for the first 20 seconds as its updating the web servers (tracking) but now I have it setup far better as in it now just checks if its been over 5 minutes since last update, and at the beginning (during debugging) I had it set to think it had just updated.
So, at first no matter what I did it just would be init'd by the library, but as it turns out the library isn't very good so I lowk just vibecoded a new library (with its headers, ofc) and just got it to work.
I also made it so that it would only bother checking if the ESP32 (and thus the screen drivers) were powered on.
As gemi wemi (gemini) put it, the bytes are arranged as such:
"Byte 0 (06): This is the Touch State. 06 means "Finger Pressed," and 00 (your last line) means "Finger Lifted."
Byte 1 (0A) & Byte 3 (6A high nibble): These form the X coordinate.
Byte 2 (04) & Byte 3 (6A low nibble): These form the Y coordinate."
Likewise, when I was printing out the raw values (to debug working), I could see that the values were changing based upon where I touched it, so immediately, without even gemi wemi's input, I knew that it was working.
Anyways, after I got gemi do vibecode me the new stuff I guess it just works. hmmm ... neat!
For the picture, I present you:

4/24/2026 5:00 PM - New print function
I forgot to put this in my journal about debugging like 2 ago, but I also build a new print function.
Its pretty much just like c's printf, but customized to print over usb!
void print(char* text, ...) {
ux_device_stack_tasks_run();
char buffer[1024];
va_list args;
va_start(args, text);
vsnprintf(buffer, sizeof(buffer), text, args);
va_end(args);
if (clock_state == lwPWR) {
SystemClock_ConfigUSB();
}
int len = strlen(buffer);
int offset = 0;
while (offset < len) {
int chunk_size = (len - offset > 8) ? 8 : (len - offset);
USBD_CDC_ACM_Transmit((uint8_t*)buffer + offset, chunk_size, &sent);
offset += chunk_size;
ux_device_stack_tasks_run();
}
if (clock_state == lwPWR) {
SystemClock_Config();
}
}
I pretty much already counted the hours in that journal, fyi.

4/24/2026 5:40 PM - Updated Github with detailed descriptions
I did exactly that, for everything adding descriptions, such as 'updated libraries' or 'added old main file'

characters characters characters character
4/24/2026 6 PM - Got ready to set up SPI Pins
I migrated the update pin from one of the SPI pins to just be the shutdown signal as during an update the shutdown signal is not processed.
To do that I had to swap it out everywhere, but also re-write the emergency shutdown code as otherwise it would just goof up.
New code:

4/25/2026 10 AM - GOT SPI TO WORK FIRST TRY!!!! + spent a few hours vibecoding
Yep! Just real quick got gpt to vibecode me a simple script that sets up SPI on the ESP32, and sets it up for recieving. I also made it vibecode a script for the STM32 so that it just keeps sending "O" (capital 'o').
Believe it or not, it actually worked first try. The ESP32 just kept receiving "O"s.
This was nice and simple, though, so I decided I'm going to now set it up to send/receive "Hello" or something like that. Setting up the stuff was easy enough, but here's where my skills in C come in. Or the lack there of.
So sending the single character of "O" was simple enough, but sending a string required setting up an array and stuff, and for whatever reason going uint8_t tx_buf[5] = 'Hello'; doesn't work. You have to have uint8_t tx_buf[5] = "Hello";.
Spot the difference?
Yeah one has 's and the other has "s.
That is enough to make it through a random error that my brain just couldn't process. However, since I'd seen something that looked like this problem before, I decided to change it to the "s and somehow it just fixed.
There was, of course, another problem where my lack-of-c-skills came into play.
So, afaik, the & symbol can be used to 'point' to a variable or something, but it's only needed for single variables, such as uint8_t h = "h"; and not for arrays like I have. That just felt so counterintuitive that my brain just took so long to wrap itself around it, but in the end I sort of understand.
Now, before we go further, I'd just like to say it was getting quite late and my brain started stopping to work properly. This meant that I was starting to rely more and more on GPT and less and less on myself.
TLDR: GPT just kept on putting me through way too much, including like a few hundred hallucinations, and in the end I just coded it myself, for something like 20 minutes.
So, I just kept on doing whatever GPT told me, and eventually we got to a spot where I just wanted DMA. DMA doesn't require basically and CPU and it just works in the background. So, I went down its rabbit hole, never having actually initialized DMA, and it just never worked. Eventually, I just gave up, and just control z'd everything. However, the problem with STM32CubeIDE is that it only holds the memory for like 50 or so actions, and since I had way to many, I was only able to undo like 8/10ths and had to do the rest of it myself.
Once I got back to my working 'Hello', I just literally decided to change it so that the first byte would be 255 if there was a touch or 0 if no touch, then it would do the following for the next 4 bytes:
- X-coord modulo 256
- 1 if x-coord is higher than 255 or else 0.
- Y-coord modulo 256
- 1 if y-coord is higher than 255 or else 0.
What this basically did was just have two bytes to tell me what the number was. If it's less than 256, say 255, you'd have 255%256=255 and a zero. However, if you had a number higher than or equal to 256, you'd have something like 256%256=0 and then a 1. This way to reconstruct the number I just to the first one plus 256 if the second is equal to 1.
And, believe it or not, it just straight up worked. Now, of course, like all my work, it is quite glitchy and doesn't work as nicely as I'd like for it to, but hey at least I can (most of the time) get the x/y coordinates to the ESP32!
Code
Here's the code I ended up with for the ESP32:
from machine import SPI, Pin
from time import sleep_ms
spi = SPI(
1,
baudrate=100000, # start slow for testing
polarity=0, # must match STM32 CPOL Low
phase=0, # must match STM32 CPHA 1 Edge
bits=8,
firstbit=SPI.MSB,
sck=Pin(9),
mosi=Pin(18),
miso=Pin(8)
)
def read_bytes():
tx_dummy = bytes([255]*5) # send dummy byte (0xFF)
rx = bytearray(5)
spi.write_readinto(tx_dummy, rx)
return list(rx)
while True:
value = read_bytes()
print("Raw byte:", value, end="\t")
try:
print("As str:", "".join(map(chr, value)))
except:
print("Not printable")
sleep_ms(1)
It's quite simple, just setting up SPI to match the STM32, and then just reading bytes by sending 255s.
It then just prints the raw bytes, and if for whatever reason you can actually print the numbers as text, it will. This is just mainly residual code from before. The STM32 just default sends 'Hello' unless there has been a touch, so that's also something to keep in mind.
Example
This is just a sample screenshot from the output of the ESP32.

This can be decoded into:
- 255 means valid touch
- 201 means x-coord = 201
- 0 means x-coord is not increased by 256
- 39 means y = 39
- 1 means y needs to be increased by 256 to 295
Note for reviewer: This journal took about 27 minutes to complete. This journal is for yesterday's work.
4/25/2026 4:07 PM - spent two hours fixing a problem that required just one line
Oooooh 60/60 chars!!!
I really do hate that limit though.
So, here's the problem.
As it is right now, the other things in the loop just block the touch controller for too long an so it doesn't have enough time to detect the interrupt and do its stuff (This was because it wasn't set up as an interrupt, but rather as a normal GPIO pin). So, what are my possible solutions?
Immediately, I thought of the SPI, and how if I used SPI DMA it would (almost certainly) be alright. Just one problem: IT IS NOT, IN ANY WHICH WAY, EASY.
In hindsight, there were also some other solutions I had, such as:
- Using DMA/Interrupt for I2C
- Stop looking for the interrupt.
SPI DMA
So I decided I'm not going to use GPT, so you'll see I haven't even opened it today.

(it's the 25th, btw)
But, anyways, I just thought of setting it up so that the function would use DMA and just work.
The function is pretty much just the same thing, but for whatever reason, it just wasn't very easy. (I never got it to work :( ...)
Anyways, the thing is firstly you have to set up the GPDMA and that has no documentation whatsoever for SPI use, and so I pretty much just winged it. I forgot to click 'Generate Code' after configuring the RX buffer, so that definitely could have been why my first try didn't work. But, I just went a long and set it up and compiled it. The biggest problem is that this stuff compiles properly, but then crashes at run time.
So, I was stuck there just wondering why it wasn't working.
All I was getting was straight 0s.
So, feeling beaten, I decided that I'm going to restart the DMA stuff.
This time, just following whatever research I could find, I got further. This time I had made sure to set both buffers up, and I used the RX/TX function, and not just the TX function.
When I opened the ESP32 code and ran it, this time I saw real numbers, 72 ...
The first number corresponded with 'H' so I thought all was well. What I didn't realize then was the fact that it didn't have the next ones, and instead was just straight up 72 72 72 72 72.
The biggest problem here, however, was the fact that it was just giving me 255, 255, 255, 255, 255 whenever I touched the screen, and then it would just straight up stop working and give me 0s.
I decided I was going to take the problem one step at a time, so I went ahead and thought maybe it was having a problem with the buffer not being how it liked, so I made a copy of it using (a new function to me) memset()
This didn't help at all, but oh well I guess there isn't much I can do.
OH SHOOOOT
I think I just realized why it wasn't working. I think for whatever reason it can only send one byte of data in the way that I have it set up, and thus that's why I was getting 72 or 255. In a normal 'Hello' packet, I send 72 and then 'e' 'l' 'l' 'o' and in a normal data packet I send '255' and then the data.
What if it was actually working the entire time and I didn't notice it... That would be crazy...
The 255s did look a bit off because I hadn't programmed it to do that, but I just thought that the SPI transmit pin was just getting stuck high.
Hmmmmm..... Oh well a problem for later. It works as is so...
Well, my solution was just as simple as to always read the position. This way if my finger was up, I'd have touch.touchPoints[0].state equaling false and if it was touching, it would be true.
This way, I would just update it every time the loop came around!
And I can confirm, it does work.
Now for my data, if it starts with a zero its the point where my finger lifted off, and if it starts with a 255 its where my finger is right now!
Corruption
So I was having this really weird problem where my stuff would get corrupted, and somehow increasing the baud rate to 1 MHz seemed to fix it. I also made a bunch of checks to see if it was ok, and here's my final code on the ESP32 side! Getting all these checks and timing an stuff did take longer than I'd admit, as it really is just a fine-tuning exercise.
CODE!
def read_bytes():
tx_dummy = bytes([255]*5) # send dummy byte (0xFF)
rx = bytearray(5)
spi.write_readinto(tx_dummy, rx)
return list(rx)
lv = []
while True:
value = read_bytes()
if value[2] == 0 and (value[0] == 255 or value[0] == 0) and value[4] < 2 and value[1] != 0 and value[3] != 0:
if value != lv:
print("Raw byte:", value, f" \tx: {value[1]:03d} y: {value[3] + 256*value[4]:03d} tstamp: {ticks_ms()%10000}")
lv = value
elif value == [0,0,0,0,0]:
pass
#print(value)
else:
pass
#print(value)
sleep_us(1000)
In terms of the STM32, I found that a timeout of 3ms worked best, as it was long enough that the ESP32 was reliably able to get the info clocked, but not too long as to block everything on the STM32.
Here's the relevant code.
if (esp_on) {
CST328_ReadData(&touch);
if (touch.touchPoints[0].state) {
print("X: %03d Y: %03d\n", touch.touchPoints[0].x, touch.touchPoints[0].y);
tx_buf[0] = 255;
tx_buf[1] = touch.touchPoints[0].x % 256;
tx_buf[2] = touch.touchPoints[0].x-256 > 255;
tx_buf[3] = touch.touchPoints[0].y % 256;
tx_buf[4] = touch.touchPoints[0].y > 255;
} else {
tx_buf[0] = 0;
}
}
4/25/2026 4:11 PM - the line that i fixed
In case it wasn't clear or anything, here's what it was.
In the checking for the touch, now it's just:
if (esp_on) {
whereas is was
if (HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_8) == GPIO_PIN_RESET && esp_on) {

4/26/2026 - Started Adding Wifi Support & Optimised Boot Order
Let's get the quick one out of the way: I real quick adjusted the timing of things so that it wouldn't import the unnecessary things, and the TFT would be initialized earlier making it easier seen that the board turned on.
In addition, I cleaned up the main code with the addition of clear section labels. Hope over to my github to check them out, and here's a sample.
# -------------- Function Defines ---------------- #
# To shut everything down
def shut_down():
...
async def update_mqtt():
...
# ---------------- TRACKING CODE STARTS HERE ----------------- #
def track():

Wifi
So, I was looking at how much data this uses, and it's about 0.7mb per day or ~2ยข plus the daily connection fee of ~2ยข, so I was thinking of setting up something to just use wifi if any known network is around to transmit, and then creating a library that would just handle everything, and interface with a new Wifi library and my existing Sim7080G Cell library.
I started work from scratch, and setting it up to actually connect. As I already had a script to connect to wifi, I just simply started with that. As my plan was to try and make it just scan for networks first, then compare them to the list and see if any match, I'd actually have to make a scan function that wasn't blocking like the default one.
Eventually, I stumbled upon this solution.
Even though I don't particularly like how its running the stuff non-stop on the seconds thread (core (?)) it still gets the job done and I can await it so all's well on my side.
The thing is, I never like to use code that I have to deal with every day and isn't at least understandable to me.
So, the first thing I did was, naturally, unsuccessfully try to take it apart and make it more efficient. I would have liked for it to just fire off whenever I ask it to, but alas me not understanding the code made me not be able to edit it well.
Wifi connection.
Next, I worked on getting the wifi connection thing to actually just like connect to the strongest signal, but only try to connect if it actually had credentials for it.
Here's my code, and I'll explain it.
async def _connect(self, ssid, password):
self.wlan.connect(ssid, password)
await asyncio.sleep_ms(200)
while self.wlan.status() == STAT_CONNECTING:
await asyncio.sleep_ms(300)
if self.wlan.isconnected():
self.connected_to = ssid
return True
else:
print(WLAN.status)
return False
async def connect(self, force=False):
if self.wlan.isconnected() and not force:
return True
elif force:
self.off()
await asyncio.sleep_ms(50)
self.on()
if self.no_info:
return await self._connect(self.ssid, self.password)
else:
visible_networks = []
info = await self.scan()
for network in info:
visible_networks.append(network[0].decode())
print(visible_networks)
for network in self.wifi_info:
if network in visible_networks:
if await self._connect(network, self.wifi_info[network]):
return True
return False
Basically, in the core essence it will return True if it connects successfully, and False if not.
Firstly, like many things I code, it will check whether or not the user wants to force it to connect even if it already is.
This will cause it to, if forced, turn off the wifi and turn it back on. Next up I'll check if there is only one ssid/password (and as of now it needs to be provided at init or using force_new_info()) and if so it will just asynchronously connect to the wifi.
Otherwise, if the data is provided in a list, then it will asynchronously scan for available wifis, and then check which ones you have credentials for.
If there are multiple, then we want it to connect to the one with the best signal, obviously, and luckily, the data we get from .scan() is already sorted in that order!
Thus, we just go checking one by one and connect to the first one we can!
Wifi.py!
from time import ticks_ms, sleep_ms
from network import WLAN, STA_IF, STAT_CONNECTING, STAT_GOT_IP
import _thread
import uasyncio as asyncio
class Wifi(object):
def __init__(self, ssid=None, password=None, on=False):
try:
from secrets import wifi_info
self.no_info=False
except ImportError as e:
self.no_info=True
self.wlan = WLAN(STA_IF)
self.wlan.active(on)
self._scan_start = _thread.allocate_lock()
self._scan_start.acquire()
self._scan_complete = asyncio.ThreadSafeFlag()
self._scan_thread = _thread.start_new_thread(self._scan, ())
self._scan_results = None
self.wifi_info = wifi_info
self._connected_to = None
if self.no_info:
if ssid != None:
self.ssid = ssid
else:
raise ValueError("No SSID Provided")
if password != None:
self.password = password
else:
raise ValueError("No Password Provided")
def on(self):
self.wlan.active(True)
def off(self):
self.wlan.active(False)
self._connected_to=None
def _scan(self):
while True:
self._scan_start.acquire()
try:
self._scan_results = self.wlan.scan()
except Exception as exc:
print("Exception running scan", exc)
self._scan_results = []
self._scan_complete.set()
async def scan(self, force=True):
starting_on=True
if not self.wlan.active():
starting_on=False
if force:
self.wlan.active(True)
else:
return False
self._scan_start.release()
await self._scan_complete.wait()
self._scan_complete.clear()
if not starting_on:
self.wlan.active(False)
return self._scan_results
async def _connect(self, ssid, password):
self.wlan.connect(ssid, password)
await asyncio.sleep_ms(200)
while self.wlan.status() == STAT_CONNECTING:
await asyncio.sleep_ms(300)
if self.wlan.isconnected():
self._connected_to = ssid
return True
else:
print(WLAN.status)
return False
async def connect(self, force=False):
if self.wlan.isconnected() and not force:
return True
elif force:
self.off()
await asyncio.sleep_ms(50)
self.on()
if self.no_info:
return await self._connect(self.ssid, self.password)
else:
visible_networks = []
info = await self.scan()
for network in info:
visible_networks.append(network[0].decode())
print(visible_networks)
for network in self.wifi_info:
if network in visible_networks:
if await self._connect(network, self.wifi_info[network]):
return True
return False
def force_new_info(self, ssid, password):
self.no_info = True
self.ssid = ssid
self.password = password
@property
def is_connected(self):
return self.wlan.isconnected()
@property
def connected_to(self):
return self._connected_to if self.wlan.isconnected() else None
async def test():
wifi = Wifi()
print(f"\n\n{await wifi.connect(force=True)}")
print(wifi.connected_to)
wifi.off()
if __name__ == '__main__':
asyncio.run(test())
5/1/2026 4:27 PM - [Retro] Wifi transmission (not quite good yet but works!
In case it isn't clear, this is a retro journal of my work on getting the wifi based MQTT updating started, but not yet fully functional.
I'll continue work once I finish this.
WIFIFIFIFI!!!
So yeah if you look at my work (that should be going up to github right about now), I've got some basic MQTT-ing going on, but as they are in individual libraries they aren't quite as useful as everything will be once I finish tonight, but I really had to get my data usage down, as 3~4ยข per day was adding up quickly.
So, I made some very messy code that would take care of that, but first I had to re-learn how to use MQTT. Thankfully some time ago I had already installed the web MQTT server thingy onto the ESP32 (idk when) and so it was there and ready to be used.
On the wifi side there is these functions, really stupid but they work, and yeah.
def connect_to_mqtt(self):
if self.client == None:
print("Failed to connect: No secrets file detected")
return "No secrets file detected"
try:
self.client.connect()
except Exception as e:
print(f"Failed to connect: {type(e).__name__} {e}")
def pub_lat(self, msg):
self.client.publish(self.latitude,
msg,
qos=0)
def pub_long(self, msg):
self.client.publish(self.longitude,
msg,
qos=0)
On the main.py side it's even uglier and it looks really stupid, and is as follows:
if w_connect:
print("wifi")
wifi.pub_lat(f"{gps.pos[1][0]}")
sleep_ms(6_000)
wifi.pub_long(f"{gps.pos[0][0]}")
sleep_ms(6_000)
bat_list = cell.at('CBC').split(',')
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
else:
bat_list = cell.at('CBC').split(',')
if gps.pos[1][0] != 0:
cell.send_message(f"{gps.pos[1][0]}", feed='latitude') # Send latitude
else:
cell.send_message(f"lat, {gps.pos[1][0]}, {gps.pos[0][0]}, {"/".join(map(str, gps.sats))}", feed='debug')
if gps.pos[0][0] != 0:
cell.send_message(f"{gps.pos[0][0]}", feed='longitude') # Send longitude
else:
cell.send_message(f"long {gps.pos[1][0]}, {gps.pos[0][0]}, {"/".join(map(str, gps.sats))}", feed='debug')
cell.send_message(f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
I seem to have fixed the issue as in I never get the debug calls anymore, so I can really clean up all the code.
And yeah that's pretty much what took me the majority of the time.
Really just getting the MQTT setup all working took the majority, but all in all I got everything done quite quickly.
Now what picture should i put...
oooh I have an idea... In the naming scheme I set it up so that the wifi client and the cell client have different names so they can theoretically publish at the same time!
Here's what an update looks like:

5/1/2026 4:41 PM - WEB DEV!
Now, you may be wondering why I'm doing web dev.
And so would I.
More seriously, the thing is I want to be able to use this odo for people to track the bike's speed, and thus I made a dashboard that, when the bike is in race mode, will cause it to update.
Here's and example screenshot of what the dashboard looks like.

Mind you, the code is quite good for me, as this is the second time I have ever done Javascript coding.
In the beginning I got AI to make it, but It just couldn't get it to work, the stuff was just getting cut off, so I had to take things into my own hands and fix up the code. It's honestly embarrassing that a beginner could fix the code that GPT couldn't.
Alongside the dashboard comes a new python script that you'll see labelled as race.py
The great thing is that it can also be used for measuring pretty much anything's speed, so this will also be used as our car's speedo. (It's a competition, don't worry about it)
Just so it is clear, the majority of the javascript is mine, but the majority of the css/html is GPT.
Try out my dashboard yourself here for the latest or here for the version as of journaling
I would recommend that you span ctrl + '+' to make the speedo bigger and easier to see.
Even though I know that this is webdev, it is related and compared to the total project time is so insignificant I thought it was definitely a nice way to interface with my stuff, and could be made as an example if someone else wanted to make an interface too.
The majority of the time outside of coding/debugging was trying to get vercel to work, but eventually I just figured I can connect my github and make vercel just rebuild every time a commit is pushed.
Note: to see the version that is as of journaling you might have to log in and even then it might not let you. I have tested the general dashboard to work for sure though.
5/1/2026 10 PM - MQTT Library
So I pretty much finished the work and testing of the publish side of the MQTT library, which basically provides an integration between both the wifi and cell layers, and this required a bit of change to those libraries, plus making the entirety of the MQTT library.
main.py isn't yet having the integration, that's a job for tomorrow.
Basically, I just made the mqtt library take in either a wifi object, cell object, or both.
It has some code in the __init__ function that actually took me quite a while to get the logic straight, but in the end it's quite nice. It has several custom errors it will through at you if you give it the wrong stuff. It is pretty much designed just to work with my libraries, but if someone wanted to they could of course write their own, they'd just have to make sure it passes my checks.
The init() function
Let's start off with it, here it is:
def __init__(self, cell=None, wifi=None):
if cell != None:
try:
if cell._version[0] == 1:
self.cell = cell
self.cell_setup = True
else:
raise RuntimeError("Incorrect cell object version.")
except AttributeError:
raise RuntimeError("Incorrect wifi object given. Required module is availible at 'https://github.com/roc-ket-cod-er/Cpeedo-mk5/blob/main/Firmware/ESP32/sim7080g.py'")
else:
self.cell = None
self.cell_setup = False
if wifi != None:
try:
if wifi._version[0] == 1:
self.wifi = wifi
self.wifi_setup = True
else:
raise RuntimeError(f"Incorrect wifi object version: {wifi._version[0]}")
except AttributeError:
raise RuntimeError("Incorrect wifi object given. Required module is availible at 'https://github.com/roc-ket-cod-er/Cpeedo-mk5/blob/main/Firmware/ESP32/wifi.py'")
else:
self.wifi = None
self.wifi_setup = False
if self.wifi_setup:
pass
elif self.cell_setup:
pass
else:
raise ValueError("No wifi object or cell object given.")
Basically, it starts off and checks if a cell object is given. If it is, it tries to check if library has a correct version attribute, and if so continues. If not, there are two possible errors it can throw: both RuntimeErrors, but one is if the library version is wrong, and the other is if you passed a completely wrong library which doesn't even have the ._version attribute.
A note here, for some stupid reason I have this code here:
@property
def version(self):
return "v1.0.0"
@property
def _version(self):
return [1, 0, 0]
As its in both libraries, it's pretty straight forward, however you'll not that I have the .version() as a readable attribute, and the ._version() isn't. For some reason in early debugging I had the .version() for both and I was really confused why it was giving me the 'wrong version' error. Hey, at least I can confirm it works!
Anyways, if there is no cell object, a perfectly possible scenario, it takes note of that and continues.
Then, it repeats the exact same steps for the wifi module. In fact, I wrote the wifi module by hand, then copied it for the cell one, but had to change so many things that it probably wasn't even worth it.
Next, it checks if at least one (either wifi or cell) is available, and if not raises another error.
And that's it for the init() code! It took me so incredibly long to get it to work and stuff, but now that I explain it it sounds so simple. The testing code wasn't the most cooperative either because you can't await outside of a function and unlike normal python's asnycio, micropython's uasyncio uses a different set where run() runs until complete, (so does run_until_complete()) and you need to run create_task() to run a new function asynchronously. Then I forgot to make the main loop async to actually let it run and all ... it was just a mess.
Anyways, for the messaging there is some simple code, just two individual functions wrapped in with a main one.
async def _wifimsg(self, msg, feed, qos):
if self.wifi_setup:
if self.wifi.is_on:
started = 'on'
else:
started = 'off'
self.wifi.on()
if not self.wifi.is_connected_to_wifi:
await self.wifi.aconnect()
try:
print(msg)
self.wifi.msg(msg, feed, qos)
except AttributeError:
self.wifi.connect_to_mqtt()
self.wifi.msg(msg, feed, qos)
await asyncio.sleep_ms(1500)
if started == 'off':
self.wifi.off()
return True
async def _cellmsg(self, msg, feed):
if not self.cell_setup:
return False
if self.cell.is_off:
await self.cell.aon()
started='off'
else:
started='on'
await self.cell.aconnect('io')
await self.cell.amsg(msg, feed)
return True
async def amsg(self, msg, feed, prefer="wifi", qos=0):
if prefer.lower() == "wifi":
if await self._wifimsg(msg, feed, qos):
return True
return await self._cellmsg(msg, feed)
else:
if await self._cellmsg(msg, feed):
return True
return await self._wifimsg(msg, feed, qos)
I actually spent a bunch of time trying to find out if there is a way to see if the wifi is busy or not, but that's really hard and needs qos=1, and that takes longer or something. All I know is that I didn't get a chance to test it out. Maybe a tomorrow thing...
Anyways, at first I just had one main function (this was when I only had the wifi going) but then when I wanted to add the prefer tag and have it fall back on the other I felt that two different functions were just far easier.
So, I split them up, and I think it works quite well as it is.

Obviously, I started off testing the wifi part first, and there was this thing where I just couldn't figure out how to get the coroutines and stuff to start up, but eventually I was able to by making a main() function, just for testing. Alas, it's more work than ideal, but I guess all's well that ends well...
There was this thing though that it would just end up creating a new test feed because all the publishing coroutines would run at a very similar time to each other and that just ended up making me go over my feed limit, but making it run less at the same time semi-fixed that.
Next up,
AttributeError: 'NoneType' object has no attribute 'write'
This one really stumped me.
So here's what was going on. I was running the umqtt.robust script which actually just ends up running the umqtt.simple script, but either way it was so weird how the thing gave that error.
Actually, this had also happened before and I never understood why.
As it turns out, its caused by not being connected to a MQTT server, thus wrapping it into a try: except: block can make it that if it fails then you know that it's not connected and just connect and rerun!
Simple solutions just sometime evade you... just if this stuff were easy it wouldn't have took me so gosh darned long ...
Alright well in total this took about 25 minutes to journal. Time really does fly doesn't it...
5/2/2026 - Implemented MQTT Library, learned about QOS
MQTT
Firstly, I worked on implementing the new MQTT library in, but there's this problem.
While updating, it wakes the Cell chip up to tell it the battery stats, the percentage and voltage.
However, the problem is, now that the majority of the updates will be on wifi I have to figure something new out. My solution to this was originally just to wake up the SIM7080G once every few cycles to update the servers with new battery information, but then I noticed that it only takes a few additional seconds so I decided to undo all my work and just replace it with some really simple code.
Here's my entire tracking script now, and you'll notice how it's pretty much nothing.
# ---------------- TRACKING CODE STARTS HERE ----------------- #
async def track():
try:
freq(80_000_000)
st = ticks_ms()
while ticks_ms() < 25_000 + st:
gps.update(4096)
if gps.sats[0] > 5:
break
print(gps.sats)
sleep_ms(20)
while ticks_ms() < 40_000 + st:
sleep_ms(20)
gps.update(4096)
if gps.lock:
break
gps.ban_updates()
if gps.lock:
gps.off()
cell.on()
bat_list = cell.at('CBC').split(',')
await mqtt.amsg(
f"{gps.pos[1][0]}",
"latitude"
)
await mqtt.amsg(
f"{gps.pos[0][0]}",
"longitude"
)
await mqtt.amsg(
f"{"/".join(map(str, gps.sats))}s {round(gps.speed, 2)}km/h {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s",
feed='other-info'
)
else:
cell.send_message(f"NO LOCK: {"/".join(map(str, gps.sats))}s {gps.gps.hdop} hdop {bat_list[1]}% ({bat_list[2][:-5]}mV) waited: {(ticks_ms()-st)//1000}s", feed='other-info')
except Exception as e:
print(e)
shut_down()
except KeyboardInterrupt:
return
shut_down()
The important part is that I replaced all those weird checks and functions with the new MQTT library and now all that is requires is just the awaiting of the mqtt.amsg() function.
The rest is just getting a lock and checks.

^^ you'll see it still works ^^
STM32 Code Update
I just ran a simple code update to quickly make it so that the code gives the update function 90s now and so that the STM32 just resets the ESP32 if it is updating to make sure the user doesn't have to wait anything more than necessary.
QOS
I spent some time researching what QOS really does in the MQTT context, and as it turns out giving a qos of one makes the function blocking and ensures that the server receives the message. Thus, I made it so that it would no longer await for 1500 ms arbitrarily, but now it will just set QOS=1 and so it will just block that script until the message is successfully sent for the important stuff, and for the not-important runtime stuff it won't bother blocking and stuff. (Especially because the SIM7080G just has its own methodology and stuff and that will be used 99.9% of the time.)
5/3/2026 - Fixed up MQTT Edge Cases
So I woke up to see my board glitching out angrily. Hmmmm when did it go down? To check, I'd have to use the internet, but for some reason the internet was out... Hmmmmmmmmmmm
The internet comes back
Yipeee!
Now what...
Oh yeah when did it stop working. Looking at the data, it seems that it was working normally until 9:30

And I took a look at it about 15 mins later. (When it was glitching)
Hmmmm.... anything else that might have went wrong...
I now know why the wifi went out: it was because someone knocked out the plug and caused it to go down, about 5~20 mins before I saw it glitching out...
The good thing is, my IDE doesn't require wifi or anything, (it's literally just thonny), so I fired it up and watched what my program outputted as it spiraled into chaos.
Well it was OSError: -212. Not awfully helpful.
The biggest thing is that I wasn't able to catch it. The library, umqtt.robust actually auto catches the error and just tries again. The thing is, as its not connected to the internet it will end up never working.
Once I found that part out, it was quite the easy fix.

Since wifi.aconnect already returns True/False whether or not it's connected, I can then use that to change what happens.
That's one edge case down! After some more testing, I came across another edge case.
Edge Case #2 Incorrect Password
There is also this other thing that can happen, if by some chance you happen to come across another ssid with the same name or if you have the wrong password. In this case it will just end up hanging forever and that will really not be good, so instead I've made it have a timeout option.
And actually I think that's about all I did...
It took me an hour and a half for that 
I'm actually gonna log 0.1hrs though because I think it definitely seems like I've been inflating hours, but that's just my journal quality becoming lower. I really don't know why my thoughts just aren't that clear ig...