I2C Bare-Metal Implementation

I2C Bare-Metal Implementation

I just finished the first major phase of my bare-metal USB speaker project. I built a custom I2C driver from scratch for the STM32F407G-DISC1 to communicate with the onboard CS43L22 audio DAC. I avoided HAL libraries entirely to force myself to read the manufacturer datasheets and understand the silicon. The final validation was successfully pulling the 0xE3 chip and revision ID directly from the control register.

Here are a few key things I learned from this phase:

  • Memory Architecture: How to map out sparse vs. dense hardware registers using volatile pointers to force direct memory access.
  • Driver Architecture: How to build a clean C API that cleanly separates the messy hardware polling loops from the main application logic.
  • The I2C Protocol: The physical realities of the wire, including 7-bit address shifting, data direction bits, and generating Repeated Start conditions.
  • C Preprocessor Mechanics: Managing bit-shifting macros and resolving hidden preprocessor collisions that cause silent compiler failures.

Here is what I struggled with and had to debug:

  • Floating Pins: Dealing with a bus that constantly threw a hardware BUSY flag before a single clock pulse was sent. I had to use the hardware debugger to read the Input Data Register (`IDR`) to prove the physical open-drain pins were floating, which required engaging the internal pull-up resistors (`PUPDR`).
  • Wedged State Machines: Learning that fixing the physical wires isn't always enough. I had to implement a violent software reset (`SWRST`) to flush the I2C peripheral's traumatized internal logic block.
  • The 1-Byte Read Trap: Figuring out the exact, rigid chronological sequence required to read a single byte (disabling the ACK, clearing the ADDR flag, and generating a STOP condition) without the STM32 crashing the bus.
  • Dummy Writes: Getting stuck in infinite loops because I tried to read from a register without first configuring the state machine as a Master Transmitter to send the target register address.

Here is Github repo of the project that I did: https://github.com/AaravSibbal/I2C_Check

Let me know what y'all think.

Here is what I am planning to do next:

  • Phase 2 (Hardware Interrupts): Stepping away from the audio chip to master the ARM Cortex-M4's NVIC, moving from blocking polling loops to an asynchronous, event-driven architecture.
  • Phase 3 (DMA & I2S): Configuring the clock matrix and building the architecture to move large data buffers autonomously, streaming a continuous test tone into the DAC.
  • Phase 4 (USB Audio): Implementing the USB device stack and HID controls so my computer recognizes the board as a physical audio playback device.

To view or add a comment, sign in

More articles by Aarav Sibbal

  • BLE Speaker Project Update (Phase 3)

    Recent Milestones in Bare-Metal Embedded Development I recently reached a significant checkpoint in my custom firmware…

    1 Comment
  • Getting into the weeds with bare metal STM32

    I’ve been diving into embedded systems lately and decided to move away from high-level libraries to see how things…

Others also viewed

Explore content categories