2026. március 8., vasárnap

Log No.6 of the 25/26 Hungarian National Engineering Olympiad

 Damn. A lot longer than I wanted, but I honestly forgot about this blog.

So, the PCB arrived in about mid-February, if I'm not mistaken; and it's as gorgeous as you think. I didn't get any pictures of the bare PCB, only after soldering some parts, but the minimum order quantity is 5, therefore I might upload a picture sometime. I first soldered on the Amphenol connectors for the CM5, which was a huge pain, but I managed to get it on with *enough* mechanical strength. Then I did the other basic stuff, like the USB, HDMI, USBC PWR, buttons, and all parts needed for those to work, and thankfully, it worked. Pretty well, actually. I'm surprised that the HDMI signal integrity looks to be completely fine, which means I actually managed to get the impedance matching right. This applies to the USB as well, but that isn't as strict. 
Again, it was a great relief that it turned on and had working IO, though the HDMI and USB had some unexpected clearance issues which I fixed by mutilating the USB connector.

Then I soldered on some minor SMD parts and the servo PWM controller, which I even managed to talk to with the CM5 and it could make the servo turn just fine. Then I moved on to the FTS STM32. I purposely ordered two of them, and I'm glad I did, since the first one decided to leave this realm early for whatever reason. Anyway, I set it up with the second one, and right now it's just coded to blink and LED on the dedicated FTS emergency power output, but I'll make some code for it later. 

And the 2nd latest development is the sensor-handling STM32F745VET6 and the MS5611 barometer/thermometer, which I managed to get the code working for in about 3 days. I probably could've done it faster by using AI, but did you hear that we've officially entered water bankruptcy? Because of a tool no one asked for. Sure, it's useful sometimes but it's like doing cocaine with an already sedentary lifestyle. 

That's basically all I have on the PCB front; though I did already try to solder on the BNO085, but I was having some great troubles with it, and I'm pretty sure it can't survive like... 12 heat gun cycles, so I ordered a new one. Hand-soldering LGA like this is borderline surgery.


Also! I made a website! I got a great deal for the domain name; 10USD for the first two years, and so I made basically a landing page/linktree-esque site for the project at openapollo.space. Go check it out! If it's still up, that is... It will cost 50USD a year after the first two expires. We'll see.


That's all for now, I keep doing these at 3am, I desperately need to work on my sleep schedule.
I'll include some pictures here.

TTZ













2026. január 25., vasárnap

Log No.5 of the 25/26 Hungarian National Engineering Olympiad

It's been a while. Devlog time!

The first thing that pissed me off in this grand 2026, is FreeStaticMaps API seemingly just seizing to exist. I tried to request a map in my app and got an error. At first I thought CloudFlare was down again, but I checked and the both their own site and the RapidAPI site of them completely disappeared. Without a trace. I can't even find any personal contact info on waybackmachine, and I won't even bother trying the official email of the project. So, as much as that more confused than angered me, I had to switch to mapbox. It was harder to setup than FreeStaticMaps because of the long request URL and different encoding, but I got it working and it's significantly more faster than FreeStaticMaps. Still, I wonder what happened.

I changed the code of the PIC and now instead of HEX, it sends regular numbers to represent current draw. I also added some dummy samplings to give the sampling capacitor more time to stabilize. That's all I've got on that, honestly. Took a while to figure out converting integers to chars, but then it turned out that I can use C libraries and the compiler will handle it.

I finally finished the full schematic of the PCB, routed all of it in 4 days and just ordered it from JLCPCB. I'Il include a picture of it here and one when it arrives. It looks hella cool IMO. It was also... not very cheap.

I've been having a bunch of problems trying to get the python to write to and the JS to read from the json that I'm putting the serial data in, I tried a bunch of things, then tried using tempfiles and renaming them, but it didn't wanna put it in the right directory, etc. So now I'Il just be using websocket. "Why not flask???" I hear you asking. Well, because I need the JS to read when I want it to and because flask is acting up in my venv, which I'm too lazy to try and figure out. Still don't have websocket fully setup, but it's close, I can feel it (it's definitely very far but why be negative when you can just gaslight yourself?)

That's all I can think of right now, sorry for the short segments. The length of this post doesn't make it seem as such, but I've gotten a lot done in this past month.

TTZ

Low quality pic but deal with it (around 110x140mm board if I recall well):







2025. december 30., kedd

Log No.4 of the 25/26 Hungarian National Engineering Olympiad

 Blog time!

It's only been 7 days and 27 minutes since my last blog post (yup, it's 1AM again), but I've got plenty of material.

First of all, I did manage to fix the subprocess issue that was bugging me for 3 days; like this:

                ParseSerial = subprocess.Popen( #Open and reopen every loop
                ["resources\\ParseSerial.exe"],
                stdout=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
                )
                 ParseSerial.stdin.write(data)
                 ParseSerial.stdin.flush()
                 returndata = ParseSerial.communicate()
                 print(returndata)

so it basically closes down and reopens the PIPE every loop (I guess forcing the stdin and stdout, because ParseSerial.communicate() closes the IO and gives a bunch of fun errors).
But in the end, I didn't end up using it. I realized how I don't really need much receiver-side parsing, because I can just send already easily-interpretable data from the rocket. So I switched to putting the received data into a .JSON file and reading from that.

Speaking of JSON, I added a config tab in the GUI, where you can set API key, COM channel, baud rate, and refresh frequency, so it doesn't require you to change the JSON manually or go digging around in the code and rebuild it (eww) and you can change it during runtime.

I also now have a button that you press to start sampling, and it calls all scripts needed to keep the data updated. Though this was a bit tricky, because, for the JS scripts, I first tried using await Neutralino.os.execCommand(`node ${pathtoscript}`) , which didn't work because node doesn't recognize Neutralino commands  (like await Neutralino.init() ), so I looked it up and got some info from Stackoverflow to import the JS script I want to run and call it as a function. And that worked. I also needed to call a python script to get the actual serial data, which I also did with 
await Neutralino.os.execCommand(`python &{pythonpath}`) . For a while this didn't work (even when called with 'python3'), until it did. I'm not sure what I did, but the errors went away and it can call, it now but what's weird  is that I got completely different errors when running with python or python3.

Then I noticed a big issue. When you build the app with 'neu build', it puts spews out a .exe and a resources.neu. But so far I got the paths to the Json, JS and python files by getting the absolute path and then adding "./SerialRead.py" to the end, or something along the lines. But because there are no more 'independent' files after building, you can't find the path, let alone call it, because its all in resources.neu. So I tried using the built-in NL_PATH command, which you would THINK would work because its meant for this, but it gives a single dot as path. Because of this, Instead decided to put the built app, resources.neu in a folder, along with two JSON files with the same name. Now, when editing the code, it checks whether a variable called 'const build' is set to true or false. If it's set to false, it uses the default path through the ./resources folder (so you can do 'neu run'), but if set to true, it uses the exposed json files in the folder (for the built app). I also did this with the python script, because that's not in the resources.neu file either.


I also got the RPi CM5 finally! Along with the IO board. I was really looking forward to it, because it's literally a mini PC running linux, but needless to say for 4 days I thought it was broken on arrival because I didn't know you had to attach it to the IO board with force, not just place it on there. Obvious in retrospect, but I didn't want to break it. Anyways, I'm gonna have to cancel that replacement request...
So far, it's what you'd expect; awesome af, but I haven't done much with it yet, because I haven't gotten around to making a 5V-->3.3V level shifter for UART so I can actually try and read some input without having to buy magic smoke refill.

Back to the SerialRead.py; as I mentioned, I'm not using pipes or subprocess, but changing a json file constantly. Which works, but the JS file supposed to read and display the values can't read it. And I don't get it, because the writing time of python and reading period of JS does definitely not overlap, but especially not forever. It reads it once, then stops doing so (it's in an infinite loop). But if I disconnect the Serial-to-USB adapter, killing the python script, it stars updating again and displaying the newest data. I'm really not sure what to do about this, but I'll figure it out, maybe giving the json data directly to the JS script. I'll see.

Oh and I also got an ST-LINK V2 so now I can get to coding that finally. I'm really doing anything but resting in the winter break, but I love these projects so much, this one especially.

I'm pretty sure I had like 3 more things I wanted to share, but I can't remember, so bye now.

TTZ

2025. december 23., kedd

Log No.3 of the 25/26 Hungarian National Engineering Olympiad

 


Okay, so I don't really have enough new material to make this blog post, but I'm experiencing some of the weirdest errors and behavior from my python code that gets Serial input and passes it to a string parsing C++ exe. I SHOULD be debugging it but I'm so flabbergasted that I have to share this right now.

But before that, I mentioned in my last blog how I was trying to rewrite the PIC code to make it shorter by like ~150 lines, but couldn't do that because I didn't know how to increment binary. Well, as it turns out, its literally just that. uint8_t Var = 0b00000 and then Var++. That's it, the MCU can handle it. Really tried to overcomplicate that one. Anyways, I managed to go from writing this code out 15 times

    ADCON0bits.CHS = 0b00000; //Select AN0
    __delay_ms(3); //Delay for channel switching
    ADCON0bits.GO_nDONE = 1; //Start sampling
    while(ADCON0bits.GO_nDONE == 1){ //Wait until sampling is done.
        
    }
    A0result = ((uint16_t)ADRESH << 8) | ADRESL;

to this:

    uint16_t results[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};    
    uint8_t ChSEL = 0b00000;
    char label[6];   // "A14:\0"
    label[0] = 'A';
    
    while(1){
        for(int i = 0; i <= 14; i++){
            ADCON0bits.CHS = ChSEL; //Select Channel
            __delay_ms(3); //Delay for channel switching
            ADCON0bits.GO_nDONE = 1; //Start sampling
            while(ADCON0bits.GO_nDONE == 1){ //Wait until sampling is done.

            }
            results[i] = ((uint16_t)ADRESH << 8) | ADRESL;
            
            if (ChSEL < 10) {
                label[1] = '0';
                label[2] = '0' + ChSEL;
                label[3] = ':';
                label[4] = '\0';
            } else {
                label[1] = '0' + (ChSEL / 10);
                label[2] = '0' + (ChSEL % 10);
                label[3] = ':';
                label[4] = '\0';
            }

            dectohex(results[i], label);
              ChSEL++;
    
        }
        ChSEL = 0b00000;
       __delay_ms(950);

    }
}

Much cleaner, I'd argue. 
Another thing I want to somehow rewrite, is that, currently, the ADC values get passed to UART (dectohex function) as hex codes, so it has to get decoded at the receiving computer. Which is fine, it's not that resource-intensive to turn hex to integers (CPUs like bitshifting), but it's still inconvenient. I've been using HEX because it's easier to handle as a string (well, more accurately, as an array of chars because it's C), as it doesn't need typecasting binary->int->char. But I would like to fix that and have the ADC values passed as integers so it doesn't need decoding from hex.

SECONDLY.
I tried using Windows API to get the serial input to display the data in the OpenApollo GUI, because it's low-level C and runs fast. But working with Windows API makes me want to commit unspeakable acts (I swear, this OS is held together by 30 year-old duct tape. Like DWORD is a 32bit integer because we REEALLYY need backwards compatibility to windows 98). So I gave up on that because python is fast enough.
Getting serial is easy enough, and I'm using subprocess to pass that data to the .exe. Right now, I'm only running some test script

int main() {
    std::string a;
    std::getline(std::cin, a);
    std::cout << "Hello World from C++. Got:" << a << std::endl;
    return 0;
}
//I don't like using namespace for std so I see what is std library
and I'm getting a multitude of really weird errors from python. Here's the incomplete list (I'm using subprocess.Popen):
I get the data from serial but it can't pass it because they're bytes, and it needs string. Fine. Easy fix.
    data = ser.readline().decode('ascii', errors='ignore')
Then I write this:
    AnalogParse.stdin.write(data)
It gives me a ValueError of: I/O operation on closed file. 
SO I change it to this
    data = ser.readline().decode(encoding='ascii')
No error. I don't know why, but let's move on. I need this line:
    AnalogParse.stdin.flush()
    AnalogParse.stdin.close()
And... it doesn't recognize the flush command? It did when I was running a test.py to figure out subprocess. It points to the parentheses with 'Invalid argument'. But flush() does NOT TAKE ARGUMENTS. With OS error 22: The device does not recognize the command.
Thanks Windows, very helpful.
Also this: Exception ignored in: <_io.TextIOWrapper name=3 encoding='cp1252'>
So I remove it. Now, I get the same error with
    AnalogParse.stdin.close()
Sure, I'Il remove it, I don't need it inside the loop (I think).
I had some more errors with 'I/O operation on closed file' on the flush, but I can't recreate it so I'Il just try to remember the situation.
Because of this error, I added this line
print(type(AnalogParse.stdin.flush))
And I get:
<class 'builtin_function_or_method'>
So this has absolutely NOTHING to do with I/O
But fine, I'Il import io. Still the same error, still the same type.
I remove everything unnecessary. Now, the code does run. But it gives me empty lines. The terminal opens, and it tries to write, but it just keeps writing empty lines forever. And then, after a while, it exits because of this line:
    AnalogParse.stdin.write(data)
with 'data' being an invalid argument.  I just cannot figure out why. Here's the code so far
import serial
import subprocess

ser = serial.Serial(
    port='COM4',
    baudrate=9600,
    parity=serial.PARITY_ODD,
    stopbits=serial.STOPBITS_TWO,
    bytesize=serial.SEVENBITS
)

AnalogParse = subprocess.Popen(
    ["ParseLocation.exe"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True,
    shell = True
    )

while(True):
    data = ser.readline().decode(encoding='ascii')
    AnalogParse.stdin.write(data)
    print(AnalogParse.stdout.readline())
I do not understand.
On a side note, I also tried to remove shell=true and give it a local route, because shell adds a lot of overhead, but it just can't find it, even if I add the full C:\ path.

That's all for now.

Tamás Zétény Tóth
TTZ 

*at the PIC section, I meant float instead of int, but you get it.
 
Update: It's 4 months later, and in hindsight, these errors aren't quite deserving of the title "some of the weirdest errors and behavior". But in my defense, my main language is C++, and at the time, I hadn't used a lot of python, I got into it because of OpenApollo.

2025. december 14., vasárnap

Log No.2 of the 25/26 Hungarian National Engineering Olympiad

OTIO 2025/26: Almost done with the circuit design


Alright, it has been a bit longer since the last post than I anticipated. In fact it's been so long that I turned 15 in the middle of it lol, but here we go.

First of all, I don't think I disclosed the name of the project in the last post. So, it's called OpenApollo (with blue Nordic Inline font). 

A few changes made since last time, mainly, I didn't like how no available UARTs would've been left on the CM5, so I changed the sensor handling STM from an STM32F042K6T6TR to an STM32F745VET6, which has a 100 pins and a bunch of UARTs, so now along with the previous sensors, it also handles the GPS and the servo current sensing PIC. All this means that the user now has 2 free UART channels to tie directly to the CM5 and probably even more to tie to the sensor STM (I don't remember).

Alongside that, I started working on the GUI for the 'ground station' via the NeutralinoJS framework, which means I unfortunately had to learn HTML and JS. Truly tragic. Seriously, NeutralinoJS is great for this because it uses webview, so it's basically cross-platform by default (and also has JS natively, so using APIs is easier). So far, I've managed to get the FreeStaticMaps API working after wayy too much time. Originally, I tried to use the OpenStreetMaps Maptiles API, but I had issues with zooming in on the maps, because as it turns out, map tiles are like... weird, because they're literally tiles and due to that, zooming kinda goes in a spiral and doesn't put the coordinates I want in the center, so Static maps were what I actually needed. 
Also, why can't RapidAPI just give me code that works by default? Why have you got to hide the source code from me, oh almighty Test Endpoint button? I swear coding this is harder than the circuit design and I refuse to use AI for anything other than explaining async functions.

I did finally decide on the HC-12 as the telemetry method, and it's the boring one, but actually better than the LoRa for multiple reasons. First, unlike the LoRa, its a transciever, so I don't need 2 modules taking up more space than all the other components combined. It can get a pretty good range with just software config, but I also replaced the stock spring antenna (which I'm pretty sure is just an ornament for this tbh) with an optimal 1/4 wavelength antenna, and wow, there is a huge difference. The lowest power mode on this is -1dBm and with the stock antenna, it could barely get through one concrete wall. Now with this antenna and still the lowest power mode, I can literally go 3 stories down and almost into the basement and still mostly get a signal (that's multiple meters of concrete and monopoles aren't even that good at radiating downwards). Originally I though about using what is presumably called a dipole colinear antenna (since its a combination of a dipole and a colinear antenna. duh), but I can't fit 2 antennas into the hole and a colinear array would also be way too large at 433MHz. It's a shame, but this isn't that bad either. The last benefit is that the HC12 is an SDR (I might be really wrong about this, but bear with me), so instead of having to press the pair button on the LoRa, which might not be accessible, you can just select a channel in software and that's it.

I should also revise the code for the PIC, because right now I have this part written 12 times, because I'm not sure how to increment binary numbers via code:

    ADCON0bits.CHS = 0b00000; //Select AN0
    __delay_ms(3); //Delay for channel switching
    ADCON0bits.GO_nDONE = 1; //Start sampling
    while(ADCON0bits.GO_nDONE == 1){ //Wait until sampling is done.
        
    }
    A0result = ((uint16_t)ADRESH << 8) | ADRESL;

But this would be much prettier if made into a function, so I might have to figure that out as not to get struck down by the C gods.

Also, I have a github set up now, so go to github.com/TzEngs if you want to see some absolutely atrocious code (or nothing at the moment).

It's 2:14AM, I'm absolutely done with writing this.

Tamás Zétény Tóth
TTZ

(P.S: I made a logo to put on the circuit board and now I'm gonna start putting it at the end of the posts. I think it's cool. Though I might have to rethink the Bauhaus 93 font)







2025. október 22., szerda

Log No.1 of the 25/26 Hungarian National Engineering Olympiad

OTIO 2025/26 


I actually started this back in like June (It's mid-October now) but I just now thought about starting a blog. I'm honestly not sure who I'm writing this to, but why not? In any case, I'm 14 right now so excuse my unprofessional tone.

Well, for this year (since I also took part in this OTIO last year with PingPal) I first got the idea of a portable X-ray for which I would've learnt glassblowing to make the X-ray cathode tube (I believe that's what it's called), and while I myself would've been reasonably comfortable working with radiation, my parents weren't exactly thrilled about the idea (Jee, I wonder why) so I came up with something that I've actually been looking to do for quite a few years now; a rocket guidance system.

Since it's an innovation olympiad, I also decided to make it open-source (GitHub and Maker.io account pending lol)

First, I obviously had to select something to act as the main processor of the system, for which I chose a Raspberry Pi CM5 module. I thought about non-premade solutions like some TI microprocessors, but the CM5 has some insane processing power and RAM for the size, and already runs on Linux so I wouldnt've had to do assembly for every single thing, which would've made this project practically impossible for me.

For sensors, I currently have an MS561101BA03-50 as a barometer/altimeter and also a temperature sensor, though I don't really need one- This once can measure down to 10mbar, which is about 30k meters of altitude according to this random website, which seems plenty to me. And it draws current in the uA range, so fairly efficient. 
In addition, I have a BNO085 9-axis IC that does 3-axis acceleration, has a gyroscope and a magnetometer (compass). The good thing about this IC is that it checks all of the motion sensing parts I need with those 3 sensors i mentioned, but the accelerometer clips above 8G-forces. And since a high-speed rocket launch can get well above 8Gs (probably), I also included an ADXL375BCCZ, which can do up to 200g-forces. Plenty.
Then also a GPS module, pre-built because I'm nowhere near competent to work with RF, let alone with such high frequencies.

Handling all of these sensors (besides the GPS) will be handled by an STM32F042K6T6TR with I2C. The reason for this is to offload work from the CM5 by getting the signals processed externally in the STM, perhaps Kalman filter the accelerometer data and then send a parsed and easy-to-interpret data packet through UART to the CM5.

A second STM of the same model will act as a Flight Termination System [FTS] that acts as a watchdog. If the CM5 or the sensor STM stops responding or any flight-critical systems go down (again, this can be coded by the user, so it will engage whenever they want it to), it will trigger a programmable sequence of events such as opening MOSFETs and triggering parachute charges, cutting power to certain systems, disabling thrust, etc... Since this is one of the most crucial parts of the redundancy/safety systems, it will have its own 3.3V voltage regulator, but in case the regulator goes down (highly unlikely. How would it even?), the regulator of the Sensor STM can pick up the slack, but it doesn't backfeed the other way around due to a diode (the voltage drop is manageable, it drops to ~3V, which is still in range for the STM). 
But since all of these rely on having an actual voltage input, the FTS won't engage if power is cut to the whole PCB. Which is why it will also have its own connector for a (or multiple in parallel) 3.7V Li-Pol battery, with a diode to avoid it getting charged when main power is actually supplied, and it will also give enough voltage drop for it get into V-range for the STM. 

A PCA9685PW will drive 12 servo motor channels. Each channel will have a 2W 100mR THT resistor acting as a low-side shunt for current measurement. A PIC18F46K22 MCU (40-PDIP package) will use the ADC channels with the built-in 1.024V reference selected, and since the ADC channels are 10-bit, it will have ~1mV resolution steps (though in practice it will probably be closer to 10mV or even 100mV. Still good enough.)

I'Il say more about the telemetry in later posts, because I'm still not completely sure what to use. I currently have a 433MHz LoRa selected, which has some insane range (~10km), but it needs a very low duty cycle for transmission with it to be legal here in Hungary. Then you have your common HC-12 433MHz Transciever, but it only has a 1km range, though can transmit more. So still not sure.

I'm pretty sure that's all for now, I actually have most of the circuit done already (again, started in June), so there won't really be a lot of updates, only retroactively.

Tamás Zétény Tóth
TTZ

(P.S.: I have no idea how to write blog posts)

Log No.6 of the 25/26 Hungarian National Engineering Olympiad

 Damn. A lot longer than I wanted, but I honestly forgot about this blog. So, the PCB arrived in about mid-February, if I'm not mistaken...