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
This commit is contained in:
88
lib/app/blink_app.h
Normal file
88
lib/app/blink_app.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#ifndef BLINK_APP_H
|
||||
#define BLINK_APP_H
|
||||
|
||||
#include <hal.h>
|
||||
|
||||
/*
|
||||
* BlinkApp -- Testable blink logic, decoupled from hardware.
|
||||
*
|
||||
* Blinks an LED and reads a button. When the button is pressed,
|
||||
* the blink rate doubles (toggles between normal and fast mode).
|
||||
*
|
||||
* All hardware access goes through the injected Hal pointer. This
|
||||
* class has no dependency on Arduino.h and compiles on any host.
|
||||
*/
|
||||
class BlinkApp {
|
||||
public:
|
||||
static constexpr uint8_t DEFAULT_LED_PIN = LED_BUILTIN; // pin 13
|
||||
static constexpr uint8_t DEFAULT_BUTTON_PIN = 2;
|
||||
static constexpr unsigned long SLOW_INTERVAL_MS = 500;
|
||||
static constexpr unsigned long FAST_INTERVAL_MS = 125;
|
||||
|
||||
BlinkApp(Hal* hal,
|
||||
uint8_t led_pin = DEFAULT_LED_PIN,
|
||||
uint8_t button_pin = DEFAULT_BUTTON_PIN)
|
||||
: hal_(hal)
|
||||
, led_pin_(led_pin)
|
||||
, button_pin_(button_pin)
|
||||
, led_state_(LOW)
|
||||
, fast_mode_(false)
|
||||
, last_toggle_ms_(0)
|
||||
, last_button_state_(HIGH) // pulled up, so HIGH = not pressed
|
||||
{}
|
||||
|
||||
// Call once from setup()
|
||||
void begin() {
|
||||
hal_->pinMode(led_pin_, OUTPUT);
|
||||
hal_->pinMode(button_pin_, INPUT_PULLUP);
|
||||
hal_->serialBegin(115200);
|
||||
hal_->serialPrintln("BlinkApp started");
|
||||
last_toggle_ms_ = hal_->millis();
|
||||
}
|
||||
|
||||
// Call repeatedly from loop()
|
||||
void update() {
|
||||
handleButton();
|
||||
handleBlink();
|
||||
}
|
||||
|
||||
// -- Accessors for testing ----------------------------------------------
|
||||
bool ledState() const { return led_state_ == HIGH; }
|
||||
bool fastMode() const { return fast_mode_; }
|
||||
unsigned long interval() const {
|
||||
return fast_mode_ ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
void handleButton() {
|
||||
uint8_t reading = hal_->digitalRead(button_pin_);
|
||||
|
||||
// Detect falling edge (HIGH -> LOW = button press with INPUT_PULLUP)
|
||||
if (last_button_state_ == HIGH && reading == LOW) {
|
||||
fast_mode_ = !fast_mode_;
|
||||
hal_->serialPrintln(fast_mode_ ? "FAST" : "SLOW");
|
||||
}
|
||||
last_button_state_ = reading;
|
||||
}
|
||||
|
||||
void handleBlink() {
|
||||
unsigned long now = hal_->millis();
|
||||
unsigned long target = fast_mode_ ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS;
|
||||
|
||||
if (now - last_toggle_ms_ >= target) {
|
||||
led_state_ = (led_state_ == HIGH) ? LOW : HIGH;
|
||||
hal_->digitalWrite(led_pin_, led_state_);
|
||||
last_toggle_ms_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
Hal* hal_;
|
||||
uint8_t led_pin_;
|
||||
uint8_t button_pin_;
|
||||
uint8_t led_state_;
|
||||
bool fast_mode_;
|
||||
unsigned long last_toggle_ms_;
|
||||
uint8_t last_button_state_;
|
||||
};
|
||||
|
||||
#endif // BLINK_APP_H
|
||||
67
lib/hal/hal.h
Normal file
67
lib/hal/hal.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef HAL_H
|
||||
#define HAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Pin modes (match Arduino constants)
|
||||
#ifndef INPUT
|
||||
#define INPUT 0x0
|
||||
#define OUTPUT 0x1
|
||||
#define INPUT_PULLUP 0x2
|
||||
#endif
|
||||
|
||||
#ifndef LOW
|
||||
#define LOW 0x0
|
||||
#define HIGH 0x1
|
||||
#endif
|
||||
|
||||
// LED_BUILTIN for host builds
|
||||
#ifndef LED_BUILTIN
|
||||
#define LED_BUILTIN 13
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Hardware Abstraction Layer
|
||||
*
|
||||
* Abstract interface over GPIO, timing, serial, and I2C. Sketch logic
|
||||
* depends on this interface only -- never on Arduino.h directly.
|
||||
*
|
||||
* Two implementations:
|
||||
* hal_arduino.h -- real hardware (included by .ino files)
|
||||
* mock_hal.h -- Google Mock (included by test files)
|
||||
*/
|
||||
class Hal {
|
||||
public:
|
||||
virtual ~Hal() = default;
|
||||
|
||||
// -- GPIO ---------------------------------------------------------------
|
||||
virtual void pinMode(uint8_t pin, uint8_t mode) = 0;
|
||||
virtual void digitalWrite(uint8_t pin, uint8_t value) = 0;
|
||||
virtual uint8_t digitalRead(uint8_t pin) = 0;
|
||||
virtual int analogRead(uint8_t pin) = 0;
|
||||
virtual void analogWrite(uint8_t pin, int value) = 0;
|
||||
|
||||
// -- Timing -------------------------------------------------------------
|
||||
virtual unsigned long millis() = 0;
|
||||
virtual unsigned long micros() = 0;
|
||||
virtual void delay(unsigned long ms) = 0;
|
||||
virtual void delayMicroseconds(unsigned long us) = 0;
|
||||
|
||||
// -- Serial -------------------------------------------------------------
|
||||
virtual void serialBegin(unsigned long baud) = 0;
|
||||
virtual void serialPrint(const char* msg) = 0;
|
||||
virtual void serialPrintln(const char* msg) = 0;
|
||||
virtual int serialAvailable() = 0;
|
||||
virtual int serialRead() = 0;
|
||||
|
||||
// -- I2C ----------------------------------------------------------------
|
||||
virtual void i2cBegin() = 0;
|
||||
virtual void i2cBeginTransmission(uint8_t addr) = 0;
|
||||
virtual size_t i2cWrite(uint8_t data) = 0;
|
||||
virtual uint8_t i2cEndTransmission() = 0;
|
||||
virtual uint8_t i2cRequestFrom(uint8_t addr, uint8_t count) = 0;
|
||||
virtual int i2cAvailable() = 0;
|
||||
virtual int i2cRead() = 0;
|
||||
};
|
||||
|
||||
#endif // HAL_H
|
||||
93
lib/hal/hal_arduino.h
Normal file
93
lib/hal/hal_arduino.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef HAL_ARDUINO_H
|
||||
#define HAL_ARDUINO_H
|
||||
|
||||
/*
|
||||
* Real hardware implementation of the HAL.
|
||||
*
|
||||
* This file includes Arduino.h and Wire.h, so it can only be compiled
|
||||
* by avr-gcc (via arduino-cli). It is included by .ino files only.
|
||||
*
|
||||
* Every method is a trivial passthrough to the real Arduino function.
|
||||
* The point is not to add logic here -- it is to keep Arduino.h out
|
||||
* of your application code so that code can compile on the host.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <hal.h>
|
||||
|
||||
class ArduinoHal : public Hal {
|
||||
public:
|
||||
// -- GPIO ---------------------------------------------------------------
|
||||
void pinMode(uint8_t pin, uint8_t mode) override {
|
||||
::pinMode(pin, mode);
|
||||
}
|
||||
void digitalWrite(uint8_t pin, uint8_t value) override {
|
||||
::digitalWrite(pin, value);
|
||||
}
|
||||
uint8_t digitalRead(uint8_t pin) override {
|
||||
return ::digitalRead(pin);
|
||||
}
|
||||
int analogRead(uint8_t pin) override {
|
||||
return ::analogRead(pin);
|
||||
}
|
||||
void analogWrite(uint8_t pin, int value) override {
|
||||
::analogWrite(pin, value);
|
||||
}
|
||||
|
||||
// -- Timing -------------------------------------------------------------
|
||||
unsigned long millis() override {
|
||||
return ::millis();
|
||||
}
|
||||
unsigned long micros() override {
|
||||
return ::micros();
|
||||
}
|
||||
void delay(unsigned long ms) override {
|
||||
::delay(ms);
|
||||
}
|
||||
void delayMicroseconds(unsigned long us) override {
|
||||
::delayMicroseconds(us);
|
||||
}
|
||||
|
||||
// -- Serial -------------------------------------------------------------
|
||||
void serialBegin(unsigned long baud) override {
|
||||
Serial.begin(baud);
|
||||
}
|
||||
void serialPrint(const char* msg) override {
|
||||
Serial.print(msg);
|
||||
}
|
||||
void serialPrintln(const char* msg) override {
|
||||
Serial.println(msg);
|
||||
}
|
||||
int serialAvailable() override {
|
||||
return Serial.available();
|
||||
}
|
||||
int serialRead() override {
|
||||
return Serial.read();
|
||||
}
|
||||
|
||||
// -- I2C ----------------------------------------------------------------
|
||||
void i2cBegin() override {
|
||||
Wire.begin();
|
||||
}
|
||||
void i2cBeginTransmission(uint8_t addr) override {
|
||||
Wire.beginTransmission(addr);
|
||||
}
|
||||
size_t i2cWrite(uint8_t data) override {
|
||||
return Wire.write(data);
|
||||
}
|
||||
uint8_t i2cEndTransmission() override {
|
||||
return Wire.endTransmission();
|
||||
}
|
||||
uint8_t i2cRequestFrom(uint8_t addr, uint8_t count) override {
|
||||
return Wire.requestFrom(addr, count);
|
||||
}
|
||||
int i2cAvailable() override {
|
||||
return Wire.available();
|
||||
}
|
||||
int i2cRead() override {
|
||||
return Wire.read();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // HAL_ARDUINO_H
|
||||
Reference in New Issue
Block a user