Assembly removed every layer between my code and the physical failure. A wrong register value did not produce a helpful stack trace; it produced a motor that did not turn or an LCD that stayed blank.
The problem
Between 2016 and 2019, I worked on school projects with AT89C5131A microcontrollers and Assembly. We built two separate programs: one controlled a DC motor with pulse-width modulation, another drove an LCD display through its command and data pins.
Assembly was unforgiving compared with C++ or SQF. I had to account for what every instruction and connection did. A wrong register value, a missing delay, or a loose wire all looked like the same failure from the outside.
The build
The motor project used external interrupts to raise and lower a speed level, a timer to generate the PWM timing, and a seven-segment display to show the current level. The program had to turn physical inputs into a stable speed level, keep the duty cycle within bounds, and remain responsive while the motor was running.
The LCD project was a different exercise in sequencing. The display needed a specific initialisation sequence, commands for cursor position and display mode, and a busy-flag check before each write. I spent a lot of time reading datasheets and checking wiring because the code could be logically correct and still fail if the hardware setup was wrong.
We planned, wrote, and debugged the programs together, then tested the real hardware rather than stopping at code that looked plausible. The repositories preserve the Assembly source and the project files, including the parts that are clearly student work.
One detail from the motor project stayed with me: the interrupt handlers changed the speed level and then refreshed the seven-segment output before returning. That forced me to think about which values had to remain coherent between the main loop and the interrupt routine, and what could safely be interrupted. The LCD work taught me to read a datasheet as a sequence, not a reference manual: initialisation order, cursor addressing, and the busy flag all had to be respected before the display would cooperate. Debugging a blank screen meant checking the code, the wiring, and the timing in that order.
The result
Both projects worked: the motor responded to speed changes, and the LCD displayed the intended text. More importantly, I could explain the path from a register write to a physical output. That understanding made later robotics work less mysterious.
What I took from it
These projects gave me a clearer view of the boundary between software and electronics. They taught me to read datasheets, respect timing, and debug the system rather than the code in isolation. That mindset is still useful whenever a problem crosses from an application into the machine underneath it.