Building an LLM Robot with My Son — EP 2. First AI Coding Experiment — Context Strategy Changed Everything
Building an LLM Robot with My Son — EP 2. First AI Coding Experiment — Context Strategy Changed Everything
My son gave his first AI coding command that evening.
He sat down in front of the laptop, thought for a moment about what to type, and entered this:
"stop if there's something in front of it"
Claude Code produced code. He skimmed it — couldn't tell what most of it meant. "Can I upload it?" We uploaded from Arduino IDE. He put his hand in front of the robot.
It didn't stop.
"Why isn't it working?"
I looked at the code. Claude had assigned the HC-SR04 Trig pin to pin 2. Our wiring uses pin 4. The CLAUDE.md context hadn't loaded in this session — he'd opened a new Claude Code session, and in that session there was no project context.
That one moment is the whole point of this episode.
What Happens When AI Writes Embedded Code Without Context
HC-SR04 is a well-known ultrasonic sensor. Claude knows how it works — pulse timing with pulseIn(), divide by the speed of sound, divide by 2 for one-way distance. Ask for it with no other information and you get something that works:
"Write Arduino code to measure distance with HC-SR04"
Output:
#define TRIG_PIN 2
#define ECHO_PIN 3
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
float distance = duration * 0.034 / 2;
Serial.print("Distance: ");
Serial.println(distance);
delay(500);
}
It works. The problem is it won't work in our robot.
Trig is on pin 2, Echo on pin 3. Ours are on pins 4 and 5. There's a delay(500). As explained in EP 1, any delay() in the main loop blocks everything — once we're combining motor control with sensor reading, this kills timing. No NewPing library either.
This code would work in isolation but break at the next step.
Why I Considered and Rejected RAG
There are multiple approaches to injecting domain knowledge into an AI. RAG (Retrieval-Augmented Generation) was one I considered.
RAG stores documents in a vector database. When a question comes in, it retrieves the relevant sections and injects them as context. HC-SR04 datasheet, L298N pin map, ROS2 docs — all searchable at query time.
Technically, this is completely viable. With large document collections it's actually more efficient than loading everything upfront.
But does this project need RAG? I thought about it honestly.
The domain knowledge going into the harness isn't large. One pin map table, a few lines of library rationale, ROS2 topic conventions, safety constraints. Combined, maybe 120 lines. Injecting all of that as context on every request barely registers against Claude's context window.
RAG earns its complexity when documents run into hundreds of pages, or when different parts of the documentation are relevant for different queries. Setting up a vector database for a 120-line CLAUDE.md is over-engineering. If the document grows significantly, I'll reconsider. Right now, no.
Testing: Throwing the Full Datasheet at It
Instead of RAG, I tried something simpler: paste the entire HC-SR04 datasheet into the prompt.
The HC-SR04 datasheet is a 4-page PDF. I extracted it as text and prepended it to the request:
[HC-SR04 ULTRASONIC MODULE DATASHEET]
Working Voltage: 5V
Working Current: 15mA
Working Frequency: 40Hz
Max Range: 4m
Min Range: 2cm
...
(4 pages of full text)
...
Write Arduino code to measure distance with this sensor and stop at 20cm.
Results were better. Claude read the timing diagrams and got the pulseIn timing right.
But two problems remained.
First, the pin numbers were still wrong. The datasheet shows module pin labels, not my Arduino wiring. No matter how thoroughly Claude reads the datasheet, it can't know I connected Trig to pin 4.
Second, the response got noisier. A four-page datasheet includes a lot Claude already knows, plus information irrelevant to this project. The code explanation got longer, more general. The actual things I needed — my pin map, my library choice, my constraints — weren't in there.
What this experiment confirmed: when giving AI domain knowledge, the answer isn't raw source material. It's project-specific, distilled information. Ten lines of the pin map I wrote myself beat four pages of the original datasheet.
Three Approaches, Same Request
I tested all three approaches with the same prompt.
Request: "Write Arduino code that stops when HC-SR04 reads under 20cm"
No context:
#define TRIG_PIN 2 // wrong
#define ECHO_PIN 3 // wrong
// ...
delay(100); // forbidden
Full datasheet injected:
const int trigPin = 9; // still wrong — copied from a datasheet example
const int echoPin = 10; // still wrong
// ...
delay(50); // still forbidden
CLAUDE.md loaded:
#include <NewPing.h>
#define TRIG_PIN 4 // correct
#define ECHO_PIN 5 // correct
#define MAX_DISTANCE 400
#define STOP_DISTANCE 20
NewPing sonar(TRIG_PIN, ECHO_PIN, MAX_DISTANCE);
#define IN1 8
#define IN2 9
#define ENA 10
#define IN3 11
#define IN4 12
#define ENB 13
unsigned long lastPingTime = 0;
const unsigned long PING_INTERVAL = 100;
void stopMotors() {
analogWrite(ENA, 0);
analogWrite(ENB, 0);
}
void loop() {
unsigned long now = millis();
if (now - lastPingTime >= PING_INTERVAL) {
lastPingTime = now;
unsigned int distance = sonar.ping_cm();
if (distance > 0 && distance <= STOP_DISTANCE) {
stopMotors();
}
}
}
NewPing, no delay, correct pin numbers, millis()-based async timing. Everything specified in CLAUDE.md came through.
Same model, same request. What it knows determines code quality entirely.
What Actually Happened That Evening
Back to the night my son tried.
I found out later that he'd opened a new session on his own. He just hit "New conversation" — he didn't know that in Claude Code, opening a new session re-loads CLAUDE.md from scratch. He assumed Claude would still remember things from before.
So when he typed "stop if there's something in front of it," Claude had no project context. It wrote the most generic code it could. Trig pin 2, Echo pin 3.
When he asked why it didn't work, I explained: "The robot's documentation isn't loaded in this session. It should load automatically when you start fresh, but let's check."
He opened the .claude/ folder and saw CLAUDE.md. "So why didn't it load?" That part was harder to explain — that it loads at session start, and if the file was modified or the session was unstable, the load might have missed. Whether he fully understood, I'm not sure.
Instead I taught him something simpler. "Every time you start a new conversation, before asking for code, ask this first:"
"In this project, what pin number is HC-SR04 Trig?"
He typed it. Claude answered: "Trig pin is 4, Echo pin is 5."
"Is that right?"
Yes. CLAUDE.md was loaded. He then typed "stop if there's something in front of it" again — this time the pin numbers came back correct.
That became his routine. Every new session, he starts with a context check question. A 12-year-old developed that habit himself.
The Code That Actually Worked
The final version we uploaded that evening — HC-SR04 detecting obstacles under 20cm, stopping motors:
#include <NewPing.h>
#define TRIG_PIN 4
#define ECHO_PIN 5
#define MAX_DISTANCE 400
#define IN1 8
#define IN2 9
#define ENA 10
#define IN3 11
#define IN4 12
#define ENB 13
NewPing sonar(TRIG_PIN, ECHO_PIN, MAX_DISTANCE);
unsigned long lastCheck = 0;
void motorForward(int speed) {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
analogWrite(ENA, speed);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
analogWrite(ENB, speed);
}
void motorStop() {
analogWrite(ENA, 0);
analogWrite(ENB, 0);
}
void setup() {
Serial.begin(9600);
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENA, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); pinMode(ENB, OUTPUT);
motorStop();
}
void loop() {
if (millis() - lastCheck >= 100) {
lastCheck = millis();
unsigned int dist = sonar.ping_cm();
Serial.print("dist: "); Serial.println(dist);
if (dist > 0 && dist <= 20) {
motorStop();
} else {
motorForward(150);
}
}
}
He put his hand in front. It stopped before 20cm. He pulled his hand away. It moved forward again.
"It works!"
That was the first moment in this series where AI-written code ran on the actual robot. Not the first attempt — the third. One failed because of missing context, one because of wrong pin numbers. The third worked.
What's Still Unresolved
Two things remain.
One wheel runs slightly slower than the other. Both receive the same PWM value (150) but produce different speeds. Could be motor variation, wiring resistance, or driver channel characteristics. Need a calibration routine.
And sensor values are still noisy. Sometimes 0cm or 400cm appears with nothing in range. NewPing has a filtering option we haven't used yet. The noise occasionally makes the robot stop for no apparent reason. My son keeps asking "why did it stop?" I don't have a complete answer yet.
Fixing those is the next step. AI writing the code doesn't mean we're done. Real hardware always produces new questions — and new conversations to work through them.
댓글
댓글 쓰기