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
141 lines
4.3 KiB
C++
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);
|
|
}
|