Files
anvil-bash/test/test_i2c_example.cpp
Eric Ratliff 61f4659462 Arduino CLI build system with HAL-based test architecture
Build and upload tool (arduino-build.sh):
- Compile, upload, and monitor via arduino-cli
- Device discovery with USB ID identification (--devices)
- Persistent reconnecting serial monitor (--watch)
- Split compile/upload workflow (--verify, --upload-only)
- First-time setup wizard (--setup)
- Comprehensive --help with troubleshooting and RedBoard specs

Testable application architecture:
- Hardware abstraction layer (lib/hal/) decouples logic from Arduino API
- Google Mock HAL for unit tests (exact call verification)
- Simulated HAL for system tests (GPIO state, virtual clock, I2C devices)
- Example I2C temperature sensor simulator (TMP102)
- Host-side test suite via CMake + Google Test (21 tests)

Example sketch:
- blink/ -- LED blink with button-controlled speed, wired through HAL
2026-02-14 09:50:13 -06:00

141 lines
4.3 KiB
C++

#include <gtest/gtest.h>
#include "hal.h"
#include "sim_hal.h"
// ============================================================================
// Example: I2C Temperature Sensor Simulator
//
// Demonstrates how to mock an I2C device. This simulates a simple
// temperature sensor at address 0x48 (like a TMP102 or LM75).
//
// Protocol (simplified):
// Write register pointer (1 byte), then read 2 bytes back.
// Register 0x00 = temperature (MSB:LSB, 12-bit, 0.0625 deg/LSB)
// Register 0x01 = config register
// ============================================================================
class TempSensorSim : public I2cDeviceSim {
public:
static const uint8_t ADDR = 0x48;
static const uint8_t REG_TEMP = 0x00;
static const uint8_t REG_CONFIG = 0x01;
TempSensorSim() : reg_pointer_(0), temp_raw_(0) {}
// Set temperature in degrees C (converted to 12-bit raw)
void setTemperature(float deg_c) {
temp_raw_ = static_cast<int16_t>(deg_c / 0.0625f);
}
// I2cDeviceSim interface
void onReceive(const uint8_t* data, size_t len) override {
if (len >= 1) {
reg_pointer_ = data[0];
}
}
size_t onRequest(uint8_t* buf, size_t max_len) override {
if (max_len < 2) return 0;
if (reg_pointer_ == REG_TEMP) {
// 12-bit left-aligned in 16 bits
int16_t raw = temp_raw_ << 4;
buf[0] = static_cast<uint8_t>((raw >> 8) & 0xFF);
buf[1] = static_cast<uint8_t>(raw & 0xFF);
return 2;
}
if (reg_pointer_ == REG_CONFIG) {
buf[0] = 0x60; // default config
buf[1] = 0xA0;
return 2;
}
return 0;
}
private:
uint8_t reg_pointer_;
int16_t temp_raw_;
};
// -----------------------------------------------------------------------
// Helper: read temperature the way real Arduino code would
// -----------------------------------------------------------------------
static float readTemperature(Hal* hal, uint8_t addr) {
// Set register pointer to 0x00 (temperature)
hal->i2cBeginTransmission(addr);
hal->i2cWrite(TempSensorSim::REG_TEMP);
hal->i2cEndTransmission();
// Read 2 bytes
uint8_t count = hal->i2cRequestFrom(addr, 2);
if (count < 2) return -999.0f;
uint8_t msb = static_cast<uint8_t>(hal->i2cRead());
uint8_t lsb = static_cast<uint8_t>(hal->i2cRead());
// Convert 12-bit left-aligned to temperature
int16_t raw = (static_cast<int16_t>(msb) << 8) | lsb;
raw >>= 4; // right-align the 12 bits
return raw * 0.0625f;
}
// ============================================================================
// Tests
// ============================================================================
class I2cTempSensorTest : public ::testing::Test {
protected:
void SetUp() override {
sensor_.setTemperature(25.0f);
sim_.attachI2cDevice(TempSensorSim::ADDR, &sensor_);
sim_.i2cBegin();
}
SimHal sim_;
TempSensorSim sensor_;
};
TEST_F(I2cTempSensorTest, ReadsRoomTemperature) {
sensor_.setTemperature(25.0f);
float temp = readTemperature(&sim_, TempSensorSim::ADDR);
EXPECT_NEAR(temp, 25.0f, 0.1f);
}
TEST_F(I2cTempSensorTest, ReadsFreezing) {
sensor_.setTemperature(0.0f);
float temp = readTemperature(&sim_, TempSensorSim::ADDR);
EXPECT_NEAR(temp, 0.0f, 0.1f);
}
TEST_F(I2cTempSensorTest, ReadsNegativeTemperature) {
sensor_.setTemperature(-10.5f);
float temp = readTemperature(&sim_, TempSensorSim::ADDR);
EXPECT_NEAR(temp, -10.5f, 0.1f);
}
TEST_F(I2cTempSensorTest, ReadsHighTemperature) {
sensor_.setTemperature(85.0f);
float temp = readTemperature(&sim_, TempSensorSim::ADDR);
EXPECT_NEAR(temp, 85.0f, 0.1f);
}
TEST_F(I2cTempSensorTest, UnregisteredDeviceReturnsNack) {
// Try to talk to a device that does not exist
sim_.i2cBeginTransmission(0x50);
sim_.i2cWrite(0x00);
uint8_t result = sim_.i2cEndTransmission();
EXPECT_NE(result, 0); // non-zero = error
}
TEST_F(I2cTempSensorTest, TemperatureUpdatesLive) {
sensor_.setTemperature(20.0f);
float t1 = readTemperature(&sim_, TempSensorSim::ADDR);
sensor_.setTemperature(30.0f);
float t2 = readTemperature(&sim_, TempSensorSim::ADDR);
EXPECT_NEAR(t1, 20.0f, 0.1f);
EXPECT_NEAR(t2, 30.0f, 0.1f);
}