Step 3 is the first time real hardware enters the system. This is also the step where many firmware architectures silently collapse.
Part A – English Version
Why Step 3 Is Dangerous
Up to Step 2, everything was theoretical:
- states
- control flow
- ownership
Step 3 introduces reality:
- I2C can fail
- sensors can NACK
- timing can be wrong
- hardware can disappear
This is where junior firmware often breaks its own architecture by accident.
The Common Mistake: Letting Drivers Think
A very common pattern:
int read_sensor(void)
{
for (int i = 0; i < 3; i++) {
if (i2c_read(...) == 0) {
return 0;
}
k_sleep(K_MSEC(10));
}
return -EIO;
}
This looks reasonable.
But it hides decisions:
- retry count
- retry delay
- failure severity
Those decisions now live inside the driver — where they do not belong.
The Rule Introduced in Step 3
Drivers do not retry. Drivers report.
Policy belongs to the application. Drivers provide information, not decisions.
This single rule prevents massive complexity later.
Choosing the Level of Abstraction
For Step 3, we intentionally use raw I2C, not the Zephyr sensor API.
Why?
- it makes ownership explicit
- it exposes failure clearly
- it avoids magic behavior
Abstractions can come later.
MCP9808: What We Actually Need
From the application’s point of view, MCP9808 is simple:
- initialize
- read temperature
- possibly fail
That’s it.
We model it as a subsystem, not just a function.
Sensor Context
struct mcp9808_ctx {
const struct device *i2c;
uint16_t addr;
};
No retries. No state machine. Just enough context to talk to hardware.
Minimal Driver API
int mcp9808_init(struct mcp9808_ctx *ctx);
int mcp9808_sample(struct mcp9808_ctx *ctx, int32_t *temp_mdeg);
Return values:
0on success- negative errno on failure
No sleeping. No looping.
Implementation Sketch
int mcp9808_sample(struct mcp9808_ctx *ctx, int32_t *temp_mdeg)
{
uint8_t reg = 0x05; /* ambient temp register */
uint8_t buf[2];
int ret = i2c_write_read(ctx->i2c, ctx->addr,
®, 1, buf, 2);
if (ret < 0) {
return ret;
}
int16_t raw = (buf[0] << 8) | buf[1];
raw &= 0x0FFF;
*temp_mdeg = (raw * 625) / 10;
return 0;
}
Nothing clever happens here.
Integrating Sensor Init into the State Machine
case APP_STATE_SENSOR_INIT:
ret = mcp9808_init(&ctx->sensor);
if (ret < 0) {
ctx->last_error = ret;
ctx->state = APP_STATE_ERROR;
break;
}
ctx->state = APP_STATE_NET_INIT;
break;
The driver does not decide what failure means. The application does.
Integrating Sampling
case APP_STATE_SAMPLE:
ret = mcp9808_sample(&ctx->sensor, &ctx->last_temp_mdeg);
if (ret < 0) {
ctx->last_error = ret;
ctx->state = APP_STATE_ERROR;
break;
}
ctx->state = APP_STATE_SEND;
break;
Again:
- no retry
- no delay
- no policy
Why This Scales
Later, when we add:
- retries
- backoff
- power management
We will not touch this driver.
That is the payoff of discipline.
A Reviewer’s Perspective
A reviewer can immediately see:
- where hardware is accessed
- how failure propagates
- who decides recovery
No guessing.
Final Thought (English)
Drivers should be boring reporters of reality, not decision-makers.
Step 3 enforces that boundary.
Part B – Phiên bản tiếng Việt
Vì sao Step 3 nguy hiểm
Step 3 là lúc phần cứng thật xuất hiện:
- I2C có thể lỗi
- sensor có thể không trả lời
- timing có thể sai
Đây là nơi kiến trúc thường bị phá vỡ một cách vô thức.
Lỗi phổ biến: để driver suy nghĩ
Retry trong driver có vẻ tiện.
Nhưng nó che giấu:
- policy
- timing
- mức độ nghiêm trọng
Và làm hệ thống khó kiểm soát.
Quy tắc của Step 3
Driver không retry. Driver chỉ báo cáo.
Quyết định thuộc về application.
Vì sao dùng raw I2C
- rõ ràng
- không magic
- dễ review
Abstraction để sau.
MCP9808 nhìn từ application
Chỉ cần:
- init
- sample
- báo lỗi
Không hơn.
Context sensor
struct mcp9808_ctx {
const struct device *i2c;
uint16_t addr;
};
API tối thiểu
int mcp9808_init(...);
int mcp9808_sample(...);
Không sleep. Không loop.
Tích hợp vào state machine
Application quyết định:
- lỗi có nghiêm trọng không
- đi state nào tiếp theo
Lợi ích lâu dài
Khi thêm retry, backoff, power:
- không đụng driver
- không phá kiến trúc
Góc nhìn reviewer
Reviewer hiểu ngay:
- lỗi đi đâu
- ai chịu trách nhiệm
Lời kết (Tiếng Việt)
Driver tốt là driver buồn tẻ. Nó chỉ nói sự thật, không đưa ra quyết định.
Step 3 đặt ranh giới đó.