• Hardware in the Loop: Pressure Sensors, Flow Limits, and a Phase-Lag Mystery

    pchala14 hours ago 0 comments

    The physical plumbing is finally coming together! I received the necessary Tee-connectors and an analog manometer, which allowed me to successfully plumb the pressure transducer into the hydraulic system.

    Here is what the test bench looks like right now:

    With the hardware in place, it was time to validate the software.

    1. Linearizing the Pump (The LUT)

    Vibratory pumps are notoriously non-linear. To make the PID controller's life easier, I previously built a Look-Up Table (LUT) to map the AC phase delay to actual expected pump output. I ran a quick manual power-output sweep to verify this, and the results are excellent. The LUT successfully forces the vibratory pump to behave linearly across its pressure range!

    2. Taming the Pressure (PID Control)

    With the pump behaving linearly, I closed the loop. After running some experiments with the p_kp and p_ki coefficients, I successfully brought the system under control. The machine can now rapidly hit and hold a target pressure without oscillating. (Note: Ignore the temperature line in the graph below—the NTC sensor is currently disconnected while I focus on hydraulics!)

    3. The "Shot Saver" (Dynamic Flow Limiting)

    One of the coolest software features I’ve implemented is a dynamic flow limit. In standard espresso, if your coffee puck cracks (channeling), the physical resistance disappears, the flow rate spikes, and the shot is ruined.

    I tested my safety clamp: I set a hard maximum flow limit in the active profile. As you can see in the telemetry, the exact moment the flow hits the limit, the system aggressively drops the pressure to clamp the flow rate, effectively "saving" the shot!

    4. Hardware Quirks: The 3ms DAC and a Phase Mystery

    While scrutinizing the high-speed telemetry on the oscilloscope, I noticed two fascinating quirks about the hardware.

    First, despite the pressure transducer having an "analog" voltage output, looking closely at the signal reveals a distinct digital stepping nature. It looks exactly like an internal DAC updating its output buffer every 3ms. It is plenty fast enough for PID loop, but an interesting reminder of how modern cheap "analog" sensors actually work internally.

    Second, I found a mystery: When plotting the pump's Triac trigger pulse against the pressure wave, I noticed that the absolute peak of the pressure wave happens right before the Triac trigger pulse fires. 

    At first glance, this seems impossible—how can the peak pressure arrive before the electrical signal that turns the pump on?

    I have my theories, but I want to hear from the community. Drop a comment below with your ideas on why this electromechanical phase-lag happens!

  • Triac Phase Control & PIO Magic: Taming the Pump and Measuring Flow

    pchala03/24/2026 at 12:35 0 comments

    It’s time to dive into the hardware and software that actually makes the pressure profiling possible. Controlling a vibratory water pump requires precise AC phase control. To do this, we need to detect the exact moment the AC waveform crosses zero volts, and then wait a calculated number of microseconds before firing a Triac.

    Here is the schematic I am using for the zero-cross detection and Triac firing circuit:


    And here is how the modified board turned out:

    Testing the Control Loop
    Before hooking the system up to high-pressure boiling water, I needed to verify the control loop. I set up a mock environment using a potentiometer to simulate the analog pressure transducer.

    As I twist the knob to create an artificial pressure "error," the PID controller instantly recalculates and adjusts the AC phase delay to compensate. Here is the bring-up video showing the dual-PIO system running flawlessly and maintaining stable control:


    The Secret Sauce: RP2040 Programmable I/O (PIO)

    Doing microsecond-accurate AC phase chopping on a main CPU is a nightmare—if the CPU gets distracted by Wi-Fi or UI tasks, the pump stutters. To solve this, I completely offloaded the timing to the RP2040's hardware PIO blocks.

    PIO Block 1: The Wave Measurer The first state machine measures the exact length of the AC half-wave in microseconds and pushes that data to the CPU to trigger the PID calculation.

            "wait 1 pin 0", // Wait for pin to go high
            "wait 0 pin 0", // Wait for pin to go low (detect falling edge of zero-cross)
            "mov x, !null", // Initialize X counter to 0xFFFFFFFF
            "low_loop:",
            "jmp pin, rising_edge", // If pin goes high, we found the next edge
            "jmp x--, low_loop",    // Decrement X and loop
            "rising_edge:",
            "mov isr, !x",  // ISR = NOT(X) = elapsed cycles
            "push noblock", // Push the period measurement to the RX FIFO
    

    PIO Block 2: The Triac Trigger
    The second state machine pulls the calculated microsecond delay from the PID controller, waits for the zero-cross, and fires the Triac at the perfect moment. 

            "pull block",   // Pull phase delay from TX FIFO (block if empty)
            "mov x, osr",   // Move delay value to X counter
            "wait 1 pin 0", // Wait for Zero-Cross signal high
            "wait 0 pin 0", // Wait for Zero-Cross signal low (start of half-wave)
            "lp:",
            "jmp x-- lp",       // Wait for 'X' microseconds
            "set pins, 1 [30]", // Trigger Triac (pulse high for ~30 cycles)
            "set pins, 0",      // Set Triac gate low
    

    PIO Block 3: High-Resolution Flow Measurement
    I dedicated another PIO block entirely to the Hall-effect flow meter. This PIO block precisely measures time between signal edges and pushes them to FIFO. This gives the MCU incredibly high-resolution instantaneous flow rate (ml/s) and total volume readings.

            // --- 1. MEASURE HIGH STATE ---
            "mov x, !null",
            "high_loop:",
            "jmp x-- next_high", // 1 cycle
            "next_high:",
            "jmp pin high_loop", // 1 cycle
            "mov isr, !x",       // Pin went LOW, invert X
            "push noblock",      // Push HIGH duration
            // --- 2. MEASURE LOW STATE ---
            "mov x, !null",
            "low_loop:",
            "jmp pin low_done", // 1 cycle: breaks out if pin goes HIGH
            "jmp x-- low_loop", // 1 cycle: decrements and loops if X != 0
            "jmp low_loop",     // catch the fall-through and loop back
            "low_done:",
            "mov isr, !x",
            "push noblock",

    Next step is to connect pressure transducer and adjust PID coefficients. Stay tuned!

  • Software Draft Complete & Tackling the Hardware Design

    pchala03/12/2026 at 15:22 0 comments

    Welcome to the first official update for oximite, my high-performance, asynchronous Rust controller for smart espresso machines (running on the Raspberry Pi Pico W).

    I’m thrilled to announce a major milestone: the initial software draft is largely complete!
    Even better, I’ve built out a full Hardware-In-the-Loop (HIL) simulation suite, and all of my Python-driven integration tests are passing successfully.
    With the core firmware architecture, PID loops, web interface, and state machines solidifying, it’s finally time to move towards the physical world: Hardware Design.

    First Up: AC Zero-Cross & TRIAC Pump Control

    The very first hardware hurdle I am tackling is precision control of the espresso machine's vibratory pump (like the classic Ulka pumps) alongside flow sensor calibration.

    To achieve accurate pressure profiling and flow control, I am designing a phase-angle TRIAC firing circuit synced to the 50/60Hz mains. But I am doing the zero-cross detection a bit differently to give me a distinct software advantage.
    Instead of a circuit that just blips at the exact zero-crossing point, my zero-cross detector is designed to output HIGH for one complete half-wave and LOW for the other.
    Why does this matter? Standard vibratory pumps require half-wave AC to function correctly, which is usually achieved by putting a diode in series with the pump. Because my zero-cross circuit lets the Pico W easily distinguish between the positive and negative half-cycles of the AC mains, I can write my firmware to trigger the TRIAC only on the correct half-waves, entirely skipping the others.

    As a side effect, doing this in software means oximite will be able to drive pumps that don't have an inline diode, or even pumps where the existing diode has fried and shorted out! I handle the half-wave rectification directly via smart TRIAC switching.

    Next Step: Flow Calibration

    Alongside the pump control, I am calibrating the Hall-effect flow meter.

    Stay tuned as the PCB routing begins! Let me know in the comments if you've ever dealt with fried pump diodes, or if you have any thoughts on the hardware approach. ☕⚙️