Darma

2026-05-15

Back

An embedded system is not one discipline. It is a stack of them sharing a board. At the bottom is a schematic, routed onto fiberglass that a factory in China fabricates for the cost of a coffee. Above that is the firmware that brings the silicon up: trigger pulses to a distance sensor, SPI framing to a thirty-year-old display controller, PWM to a hobby servo. Above that is the scheduler that lets those drivers run as if each were alone. Above that is the protocol that lets a phone treat the whole assembly as a REST API. To earn the word embedded, you have to be honest at every layer.

Darma is the project I built to be honest at all of them: a wall-mounted feeder and monitor for a pet, made for the final assignment of Fundamentos de Materiales at Universidad de los Andes, with Nicolas Correal, under Prof. Alba Ávila. It was the first time I sat at the bench with a board I had drawn, a microcontroller I had flashed, and a sensor array I had wired, and watched all three agree. Firmware at DarmaSource; KiCad schematic, PCB, and Gerbers at DarmaBoard.

The board

The first decision was to draw our own PCB instead of stacking modules on a breadboard. A feeder is a wet, vibrating, daily-use object; a board with screw terminals, proper power rails, and labeled headers is the difference between a class demo and something that runs for a week unattended. The MCU is an esp32doit-devkit-v1 module, on long headers so it can be lifted out for reflashing. Around it sit the parts that make the system do things: an SG90-class servo for the food trapdoor, a 12V solenoid behind a logic-level MOSFET, three HC-SR04 ultrasonic distance sensors, a DHT11 for temperature and humidity, a photoresistor for ambient light, a momentary button, and a Nokia 5110 PCD8544 display on hardware SPI.

Component selection was the part of the course the syllabus cared about. The HC-SR04 expects its echo back at 5V, so the board divides each echo down before it touches the ESP32, a 3.3V part. The solenoid wanted a flyback diode and a gate resistor on the MOSFET, both of which exist on the board because they did not exist on the breadboard and we lost a transistor finding out. The 5110 needs 3.3V logic and a higher rail for the LED backlight; the silkscreen carries two supplies near it. None of these are exotic. They are the decisions you make once, on paper, when you stop stacking modules and start drawing copper. Two layers, hand-routed, ground pour on the back; Gerbers to JLCPCB; a week later, five identical boards in an envelope.

Bring-up and drivers

The firmware is C++ on the Arduino framework via PlatformIO, pinned to a small set of libraries: U8g2 for the 5110, ESP32Servo for the servo PWM, the Adafruit DHT and Unified Sensor libraries, and ArduinoJson for the API. The HC-SR04 is the cleanest example of the work below the application layer. Each sensor wants a 10-microsecond pulse on TRIG and returns a high pulse on ECHO whose width, in microseconds, encodes the round-trip time of an ultrasonic ping. Distance is duration * 0.034 / 2, in centimeters, capped at fifty so a missed echo cannot poison the rolling buffer. Three sensors, three buffers of ten samples each: bowl, front of the unit, water reservoir. The 5110, on hardware SPI, takes a four-wire framing from U8g2 and renders the six live values onto an 84-by-48 pixel field that has not aged a day since 1998.

Three tasks, two cores

The ESP32 has two Xtensa cores and runs FreeRTOS by default, so the only real question is how to slice the work. Darma runs three pinned tasks. Core 1 holds hardware, which polls every sensor each second and, as a hard rule that does not go through the network, opens the trapdoor when the front-facing sensor sees the pet within thirty centimeters. Core 1 also holds pantalla, the display task, at higher priority, refreshing the 5110 every four hundred milliseconds against the same shared buffers the sensor loop is writing. Core 0 holds wifi, which runs the access point and HTTP server with a ten-tick vTaskDelay so it never starves the watchdog.

Network on core 0, real-time work on core 1: the textbook layout, for the textbook reason that a slow HTTP client must never delay an ultrasonic ping. Shared state is six fixed-size integer buffers, written by the hardware task and read by both the display task and the API handlers. Aligned 32-bit loads and stores are atomic on the Xtensa cores, so a reader never observes a half-written word; the worst case is a stale reading, not a torn one. The race we are not protecting against is the race we are willing to lose: a phone sees, at worst, one stale reading per poll. The animal does not care about lock-free queues; the animal cares that the door opens when it walks up.

The API and the honesty

The ESP32 brings up a Wi-Fi access point named Darma, so the device needs no router. A phone joins the AP and talks to a small REST surface on port 80: GET /data dumps all six rolling buffers as one JSON document, POST /servo drives the trapdoor to a specific angle, POST /comer triggers a feed cycle, POST /agua opens the solenoid for a second. CORS is enabled so the controller can be served as static HTML. Building the JSON on a StaticJsonDocument<3000> keeps the device off the heap during a response, which on a long-running embedded target is the difference between a service that runs for a week and one that runs for an hour.

The piece I will not pretend about is the DHT11. In the working build, temperature and humidity are drawn from random() inside reasonable bounds (22 to 24 degrees Celsius, 48 to 50 percent RH). The sensor is on pin 17 and the driver is wired; it was glitching during the final sprint, and rather than ship a demo that read NaN we shipped one that read a plausible scalar. A v2 replaces it with an I2C part (SHT31 or BME280) and does not need to lie. The placeholder stays in the source because the honest record is more useful than a clean one.

Drawing a board, bringing it up, scheduling the work, and exposing it over a network are the four layers an embedded system has, and the only way to learn the joints is to build something that needs all four at once. A wall-mounted feeder turns out to be a good excuse.

CC BY-NC 4.0 © ma-r-s

?