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
257 lines
7.5 KiB
C++
257 lines
7.5 KiB
C++
#ifndef SIM_HAL_H
|
|
#define SIM_HAL_H
|
|
|
|
#include "hal.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <queue>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
/*
|
|
* Simulated HAL for system tests.
|
|
*
|
|
* Unlike MockHal (which verifies call expectations), SimHal actually
|
|
* maintains state: pin values, a virtual clock, serial output capture,
|
|
* and pluggable I2C device simulators.
|
|
*
|
|
* This lets you write system tests that exercise full application logic
|
|
* against simulated hardware:
|
|
*
|
|
* SimHal sim;
|
|
* BlinkApp app(&sim);
|
|
* app.begin();
|
|
*
|
|
* sim.setPin(2, LOW); // simulate button press
|
|
* sim.advanceMillis(600); // advance clock
|
|
* app.update();
|
|
*
|
|
* EXPECT_EQ(sim.getPin(13), HIGH); // check LED state
|
|
*/
|
|
|
|
// --------------------------------------------------------------------
|
|
// I2C device simulator interface
|
|
// --------------------------------------------------------------------
|
|
class I2cDeviceSim {
|
|
public:
|
|
virtual ~I2cDeviceSim() = default;
|
|
|
|
// Called when master writes bytes to this device
|
|
virtual void onReceive(const uint8_t* data, size_t len) = 0;
|
|
|
|
// Called when master requests bytes; fill response buffer
|
|
virtual size_t onRequest(uint8_t* buf, size_t max_len) = 0;
|
|
};
|
|
|
|
// --------------------------------------------------------------------
|
|
// Simulated HAL
|
|
// --------------------------------------------------------------------
|
|
class SimHal : public Hal {
|
|
public:
|
|
static const int NUM_PINS = 20; // D0-D13 + A0-A5
|
|
|
|
SimHal() : clock_ms_(0), clock_us_(0) {
|
|
memset(pin_modes_, 0, sizeof(pin_modes_));
|
|
memset(pin_values_, 0, sizeof(pin_values_));
|
|
}
|
|
|
|
// -- GPIO ---------------------------------------------------------------
|
|
void pinMode(uint8_t pin, uint8_t mode) override {
|
|
if (pin < NUM_PINS) {
|
|
pin_modes_[pin] = mode;
|
|
// INPUT_PULLUP defaults to HIGH
|
|
if (mode == INPUT_PULLUP) {
|
|
pin_values_[pin] = HIGH;
|
|
}
|
|
}
|
|
}
|
|
|
|
void digitalWrite(uint8_t pin, uint8_t value) override {
|
|
if (pin < NUM_PINS) {
|
|
pin_values_[pin] = value;
|
|
gpio_log_.push_back({clock_ms_, pin, value});
|
|
}
|
|
}
|
|
|
|
uint8_t digitalRead(uint8_t pin) override {
|
|
if (pin < NUM_PINS) return pin_values_[pin];
|
|
return LOW;
|
|
}
|
|
|
|
int analogRead(uint8_t pin) override {
|
|
if (pin < NUM_PINS) return analog_values_[pin];
|
|
return 0;
|
|
}
|
|
|
|
void analogWrite(uint8_t pin, int value) override {
|
|
if (pin < NUM_PINS) pin_values_[pin] = (value > 0) ? HIGH : LOW;
|
|
}
|
|
|
|
// -- Timing -------------------------------------------------------------
|
|
unsigned long millis() override { return clock_ms_; }
|
|
unsigned long micros() override { return clock_us_; }
|
|
void delay(unsigned long ms) override { advanceMillis(ms); }
|
|
void delayMicroseconds(unsigned long us) override { clock_us_ += us; }
|
|
|
|
// -- Serial -------------------------------------------------------------
|
|
void serialBegin(unsigned long baud) override { (void)baud; }
|
|
void serialPrint(const char* msg) override {
|
|
serial_output_ += msg;
|
|
}
|
|
void serialPrintln(const char* msg) override {
|
|
serial_output_ += msg;
|
|
serial_output_ += "\n";
|
|
}
|
|
int serialAvailable() override {
|
|
return static_cast<int>(serial_input_.size());
|
|
}
|
|
int serialRead() override {
|
|
if (serial_input_.empty()) return -1;
|
|
int c = serial_input_.front();
|
|
serial_input_.erase(serial_input_.begin());
|
|
return c;
|
|
}
|
|
|
|
// -- I2C ----------------------------------------------------------------
|
|
void i2cBegin() override {}
|
|
|
|
void i2cBeginTransmission(uint8_t addr) override {
|
|
i2c_addr_ = addr;
|
|
i2c_tx_buf_.clear();
|
|
}
|
|
|
|
size_t i2cWrite(uint8_t data) override {
|
|
i2c_tx_buf_.push_back(data);
|
|
return 1;
|
|
}
|
|
|
|
uint8_t i2cEndTransmission() override {
|
|
auto it = i2c_devices_.find(i2c_addr_);
|
|
if (it == i2c_devices_.end()) return 2; // NACK on address
|
|
it->second->onReceive(i2c_tx_buf_.data(), i2c_tx_buf_.size());
|
|
return 0; // success
|
|
}
|
|
|
|
uint8_t i2cRequestFrom(uint8_t addr, uint8_t count) override {
|
|
i2c_rx_buf_.clear();
|
|
auto it = i2c_devices_.find(addr);
|
|
if (it == i2c_devices_.end()) return 0;
|
|
uint8_t tmp[256];
|
|
size_t n = it->second->onRequest(tmp, count);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
i2c_rx_buf_.push_back(tmp[i]);
|
|
}
|
|
return static_cast<uint8_t>(n);
|
|
}
|
|
|
|
int i2cAvailable() override {
|
|
return static_cast<int>(i2c_rx_buf_.size());
|
|
}
|
|
|
|
int i2cRead() override {
|
|
if (i2c_rx_buf_.empty()) return -1;
|
|
int val = i2c_rx_buf_.front();
|
|
i2c_rx_buf_.erase(i2c_rx_buf_.begin());
|
|
return val;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Test control API (not part of Hal interface)
|
|
// ====================================================================
|
|
|
|
// -- Clock control ------------------------------------------------------
|
|
void advanceMillis(unsigned long ms) {
|
|
clock_ms_ += ms;
|
|
clock_us_ += ms * 1000;
|
|
}
|
|
|
|
void setMillis(unsigned long ms) {
|
|
clock_ms_ = ms;
|
|
clock_us_ = ms * 1000;
|
|
}
|
|
|
|
// -- GPIO control -------------------------------------------------------
|
|
void setPin(uint8_t pin, uint8_t value) {
|
|
if (pin < NUM_PINS) pin_values_[pin] = value;
|
|
}
|
|
|
|
uint8_t getPin(uint8_t pin) const {
|
|
if (pin < NUM_PINS) return pin_values_[pin];
|
|
return LOW;
|
|
}
|
|
|
|
uint8_t getPinMode(uint8_t pin) const {
|
|
if (pin < NUM_PINS) return pin_modes_[pin];
|
|
return 0;
|
|
}
|
|
|
|
void setAnalog(uint8_t pin, int value) {
|
|
analog_values_[pin] = value;
|
|
}
|
|
|
|
// -- GPIO history -------------------------------------------------------
|
|
struct GpioEvent {
|
|
unsigned long timestamp_ms;
|
|
uint8_t pin;
|
|
uint8_t value;
|
|
};
|
|
|
|
const std::vector<GpioEvent>& gpioLog() const { return gpio_log_; }
|
|
|
|
void clearGpioLog() { gpio_log_.clear(); }
|
|
|
|
// Count how many times a pin was set to a specific value
|
|
int countWrites(uint8_t pin, uint8_t value) const {
|
|
int count = 0;
|
|
for (const auto& e : gpio_log_) {
|
|
if (e.pin == pin && e.value == value) ++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// -- Serial control -----------------------------------------------------
|
|
const std::string& serialOutput() const { return serial_output_; }
|
|
void clearSerialOutput() { serial_output_.clear(); }
|
|
|
|
void injectSerialInput(const std::string& data) {
|
|
for (char c : data) {
|
|
serial_input_.push_back(static_cast<uint8_t>(c));
|
|
}
|
|
}
|
|
|
|
// -- I2C device registration --------------------------------------------
|
|
void attachI2cDevice(uint8_t addr, I2cDeviceSim* device) {
|
|
i2c_devices_[addr] = device;
|
|
}
|
|
|
|
void detachI2cDevice(uint8_t addr) {
|
|
i2c_devices_.erase(addr);
|
|
}
|
|
|
|
private:
|
|
// GPIO
|
|
uint8_t pin_modes_[NUM_PINS];
|
|
uint8_t pin_values_[NUM_PINS];
|
|
std::map<uint8_t, int> analog_values_;
|
|
std::vector<GpioEvent> gpio_log_;
|
|
|
|
// Timing
|
|
unsigned long clock_ms_;
|
|
unsigned long clock_us_;
|
|
|
|
// Serial
|
|
std::string serial_output_;
|
|
std::vector<uint8_t> serial_input_;
|
|
|
|
// I2C
|
|
uint8_t i2c_addr_ = 0;
|
|
std::vector<uint8_t> i2c_tx_buf_;
|
|
std::vector<uint8_t> i2c_rx_buf_;
|
|
std::map<uint8_t, I2cDeviceSim*> i2c_devices_;
|
|
};
|
|
|
|
#endif // SIM_HAL_H
|