Work on a project or a skill and write about it every day in the month of December.
I'd been wanting to do this challenge for a while now, but December is a busy month. The week-long "special Spring edition" looks like it'll be a better fit. I'll be including at least brief updates in my day log and copying or expanding on them here so they aren't lost when the relevant day log entry finally drops off the bottom of the page.
This week I will be exploring bare metal programming with the Raspberry Pi, with an eye toward getting it to send MIDI messages over a 3.5mm TRS jack based on values read from an array of 80 potentiometers and 16 toggle switches. I have a Raspberry Pi 4 on hand, which I'll be using until I can get a Raspberry Pi Zero 2. It should be a close enough match to the Zero 2 that switching from one to the other hopefully won't take much work.
Last day of the adventure, but sadly had no time for it. Getting into bare metal programming on the Raspberry Pi did whet my appetite for it, though, and strengthened my interest in someday trying my hand at real OS development. As it stands, I still need to get I2C and SPI stood up on my Raspberry Pi, and I'll need to port my code from the Raspberry Pi 4 to the Raspberry Pi Zero 2 before I go shoving it into my mixer case. I think this will be pretty easy, as the Raspberry Pi Zero 2 is effectively a miniaturized Raspberry Pi 3.
In theory, all of this is easy, but working this close to the metal is something I find intimidating (and probably will for a good while yet), hence my choosing this for this week of springtime adveturing. I didn't get as much time to work on it as I'd hoped and ran into a fairly basic issue which ate up a lot of time, but still, the progress I've made has been encouraging.
I did wind up returning the ADC board I'd ordered off eBay. The seller was one of those people just re-listing things from Amazon at a markup. I've ordered another ADC board that doesn't appear to be an Amazon re-listing, appears to be better-suited for my use case, and is cheaper.
Spent nearly all day fighting with the Raspberry Pi and digging through documentation to try to get a reliable UART connection. I'd not managed to get a connection at all via UART0 using the code off the OSDev wiki, so I tried a few other Raspberry Pi 4 kernels. The code at rpi4os.com, which used UART1 ("miniUART") instead got me garbled output at first, then sometimes correct output, which drove me nuts. I tried a few other kernels with the same results, so I began to suspect the miniUART. I returned to getting UART0 to work. Finally I seem to have worked out a config.txt that allows UART0 usage. I'm beginning to understand, on a more visceral level, why people hate having the GPU involved in booting and configuring the hardware. Feels like an additional layer to have to come to grips with, like it's sitting between me and the CPU (which, sure thing, it is).
Still getting framing issues, but I seem to have a reliable workaround in place where I run a Python script I found that monitors /dev/ttyUSB0, exit out, and then run minicom. Seems to fix the problem pretty reliably. Need to figure out why it does, when nothing else has. Might just be an issue with minicom, but I'm about at the end of my day now, so I'll investigate later.
Also spent some time trying to get my multicore code working on actual hardware, but no dice there.
Determined that my present problems are owed to misconfiguration of my tty device. Checked the output of stty -F /dev/ttyUSB0 -g before and after running the Python script I'd been using as a workaround and noted it was different. Saved the post-script output and have passed those settings to stty after connecting and disconnecting my UART to USB adapter, restarting minicom, and power cycling the Raspberry Pi a few times, as well as in the middle of a minicom session where the output was scrambled. I've added this file to my firmware repository.
The issue seems to be that minicom has not been setting the baud rate. By comparing the outputs of stty -F /dev/ttyUSB0 -a before and after applying the "magic" settings file, I noticed the baud rate was set to 9600 before applying the "fix" file and 115200 after. After some testing with minicom, I was able to confirm that minicom has not been operating at the baud rate shown in its UI. I'm a bit frustrated about this. But at least setting the rate in stty fixes it, at least until the adapter is unplugged and plugged in again.
Got some time to try getting my code running on actual hardware. Seems to run, but getting nothing but garbage over the UART, so something's amiss.
I did nothing today. Spent a little time trying to debug and puzzle over the broken eurorack module I've been working on, and household and parenting tasks took up the rest of the time.
Received a shipment of PCBs, the assembly of which might eat up all my Adventure time today. Still, I got a few utility functions I'd been missing from the C standard library written and came up with a way to run unit tests (over UART, in QEMU). If time allows, I'll dig out a serial-to-USB adapter and try running my code on the actual target hardware.
TBH, this has so far involved much less assembly programming than I'd anticipated, and what little I did need was pretty easy to pick up. It's making me more sanguine about the prospects of one day programming either my own moderately-useful OS kernel, or my own userland and drivers for something like seL4.
And of course, I can't help but wonder what it'd take to do some bare-metal programming on the MNT Reform.
ADC board arrived, much much faster than anticipated. Has vertical headers which I will need to replace with right-angle headers for it to fit in the space I have for it. No further progress on the Adventure owing to assembling the PCBs that had arrived.
Went ahead and changed the boot assembly code to give each core its own "main" function, putting the core into "low energy mode" if said function ever returns. I intend to do more today; I want to set up a section of memory representing the slider and toggle values for one core to update continuously and for the other cores to react to, also continuously.

Today I learned that Raspberry Pis don't have ADCs. I'd gotten used to MCUs that always had at least one, so I'd taken that for granted. I've ordered an ADC breakout board that should work well enough. Won't arrive until after the challenge is over, so that's going to put a bit of a damper on things.
I think I've figured out why QEMU wasn't booting from my "kernel8.img" file. I'd been passing it in with the -kernel flag when I apparently should have been using the -device flag. Tomorrow I'll try booting it on actual hardware, just to gain a little confidence in what I've got so far. Might start writing MIDI library code or working on getting sound out of it, too. Would be neat to have a little generative synth all written up and ready for potentiometer control to be added when my ADC gets here.
Spent this day fussing with ARM64 assembly code. I've never written assembly with any particular purpose before, so I'm coming into this with virtually no knowledge. I was able to consult tutorials like the one at rpi4os.com and on the OS Dev Wiki to get things rolling, but as I started playing with multicore execution using the rpi4os.com code, I found trouble.
I changed the rpi4os.com code from having a pair of functions, one "run" function and one "clear" function, per core to having a single pair which take a core_t (a simple enum) parameter. To test things, I wrote a couple "hello" functions (as I hadn't written a way to pass parameters to the functions being run on each core) which I sent off with "run" to each core and then immediately called "clear." Because "run" and "clear" just set values in memory locations which each core checks upon waking (triggered with a "sev" signal), there's a race condition where that memory location may be cleared before the target core has had a chance to copy its value into a register. Putting the "clear" command in the function to be run itself resolves this issue, but then the functions are tightly bound to a single core.
Late last night I began playing with using a wrapper function to run the clear command after running the target function. While the target function ran fine, the clear command wasn't taking effect.
I don't have a particular need to run one-off functions on any given core. My use case can actually be satisfied with one hard-coded function per core, running forever in a loop. The fact that I don't understand what's going on here bothers me, though, and points to the potential for further trouble down the road as whatever misunderstanding I'm operating under may rear its head again, especially as my codebase begins to gain complexity.
For the sake of getting on with it, I may choose to switch to a model where there's a different main function per core, but I'd like to revisit this problem again when I have a little more time. I'd hoped to have this MIDI mixer project functional by next month in order to use it for an open mic performance. The window of opportunity there is quickly closing. TBH, I probably already don't have quite enough time to figure out a set, but who knows: maybe I'll be able to put something together faster than I expect.
Accidental preliminary work. Unaware that the spring edition of the December Adventure was starting the next day, but fortunately timed, I began following the Raspberry Pi Bare Bones tutorial on the OSDev Wiki. It went extremely well: the GCC cross-compiler compilation subtask went faster than I'd expected, with the only trouble I experienced stemming from the fact that I'd tried at first to do it from a path that contained spaces. Irritating that this is still an issue in $CURRENT_YEAR, but it is what it is.
I completed the tutorial and wound up with a "kernel.elf" file that does boot in QEMU and a "kernel8.img" file that does not. I don't know why this is yet; I've reviewed everything multiple times and all seems in order. Otherwise, I successfully worked out how to stop three of the four cores so that the "Hello World" message doesn't get printed four times in parallel. (The OSDev Wiki says these should be stopped by default under the current Raspbery Pi firmware, but this is not the case under QEMU, and I don't imagine it's the case either for the old Raspbery Pi 4 I've been hanging onto these past few years.)
My boot.S file is as follows:
// AArch64 mode
// To keep this in the first portion of the binary.
.section ".text.boot"
// Make _start global.
.globl _start
.org 0x80000
// Entry point for the kernel. Registers:
// x0 -> 32 bit pointer to DTB in memory (primary core only) / 0 (secondary cores)
// x1 -> 0
// x2 -> 0
// x3 -> 0
// x4 -> 32 bit kernel entry point, _start location
_start:
// halt all but one core
mrs x0, mpidr_el1
and x0, x0, 0x3
cbnz x0, halt
// set stack before our code
ldr x5, =_start
mov sp, x5
// clear bss
ldr x5, =__bss_start
ldr w6, =__bss_size
1: cbz w6, 2f
str xzr, [x5], #8
sub w6, w6, #1
cbnz w6, 1b
// jump to C code, should not return
2: bl kernel_main
// for failsafe, halt this core
halt:
wfe
b haltIt is mostly copied directly from the OSDev Wiki, but I added the chunk which halts "all but one core."
I'll get a repository with everything else set up on Codeberg and posted under tomorrow's entry.