Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions boards/inhero_mr2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
[
"0x239A",
"0x8029"
],
[
"0x239A",
"0x0029"
],
[
"0x239A",
"0x002A"
],
[
"0x239A",
"0x802A"
]
],
"usb_product": "Inhero MR2",
"mcu": "nrf52840",
"variant": "Inhero_MR2_Board",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": [
"bluetooth"
],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino"
],
"name": "Inhero MR2",
"upload": {
"maximum_ram_size": 235520,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://inhero.de",
"vendor": "Inhero GmbH"
}
70 changes: 69 additions & 1 deletion src/helpers/AutoDiscoverRTCClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,69 @@ static bool rtc_8130_success = false;
#define PCF8563_ADDRESS 0x51
#define RX8130CE_ADDRESS 0x32

#if defined(INHERO_MR2)
static bool configureMr2RtcBackup(TwoWire& wire) {
// The library's EEPROM helpers omit the command delays and I2C error checks.
auto read = [&wire](uint8_t reg, uint8_t& value) {
wire.beginTransmission(RV3028_ADDRESS);
wire.write(reg);
if (wire.endTransmission(false) != 0 ||
wire.requestFrom((uint8_t)RV3028_ADDRESS, (uint8_t)1) != 1) return false;
value = wire.read();
return true;
};
auto write = [&wire](uint8_t reg, uint8_t value) {
wire.beginTransmission(RV3028_ADDRESS);
wire.write(reg);
wire.write(value);
return wire.endTransmission() == 0;
};
auto waitReady = [&read]() {
const uint32_t start = millis();
do {
uint8_t status;
if (!read(0x0E, status)) return false;
if ((status & 0x80) == 0) return true; // EEbusy
delay(1);
} while ((uint32_t)(millis() - start) < 500);
return false;
};
auto startBackupRead = [&]() {
return write(0x25, 0x37) && write(0x27, 0x00) && write(0x27, 0x22);
};

uint8_t control1;
if (!read(0x0F, control1)) return false;
const bool ok = [&]() {
if (!write(0x0F, control1 | 0x08) || !waitReady()) return false; // EERD
if (!startBackupRead()) return false;
delay(1); // RV-3028 manual 4.6.7: wait before checking EEbusy after a read.
uint8_t stored;
if (!waitReady() || !read(0x26, stored)) return false;
// No backup battery: BSM=00, TCE=0, BSIE=0, FEDE=1. Keep EEOffset[0]/TCR.
const uint8_t desired = (stored & 0x83) | 0x10;
if (stored != desired) {
// Write only EEPROM byte 0x37, never issue an Update All command.
if (!write(0x25, 0x37) || !write(0x26, desired) ||
!write(0x27, 0x00) || !write(0x27, 0x21)) return false;
delay(10); // RV-3028 manual 4.6.7: wait before checking EEbusy after a write.
if (!waitReady() || !startBackupRead()) return false;
delay(1);
uint8_t verified;
if (!waitReady() || !read(0x26, verified) || verified != desired) return false;
}
// A single-byte EEPROM write does not update the active RAM mirror.
uint8_t active;
return write(0x37, desired) && read(0x37, active) && active == desired;
}();

// Re-enable automatic refresh on both success and failure, without recovery.
const bool released = write(0x0F, control1 & ~0x08);
uint8_t finalControl1;
return ok && released && read(0x0F, finalControl1) && (finalControl1 & 0x08) == 0;
}
#endif

bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
Expand All @@ -36,9 +99,14 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
if (i2c_probe(wire, RV3028_ADDRESS)) {
rtc_rv3028.initI2C(wire);
rtc_rv3028.writeToRegister(0x35, 0x00);
#if defined(INHERO_MR2)
// MR2 has no backup battery: VDD and VBACKUP share the 3.3 V supply.
rv3028_success = configureMr2RtcBackup(wire);
#else
rtc_rv3028.writeToRegister(0x37, 0xB4); // Direct Switching Mode (DSM): when VDD < VBACKUP, switchover occurs from VDD to VBACKUP
rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
rv3028_success = true;
#endif
rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
}

if (i2c_probe(wire, PCF8563_ADDRESS)) {
Expand Down
108 changes: 108 additions & 0 deletions test/mr2_ina_alert/fixture.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include <cstdint>
#include <functional>
#include <iostream>
#include <stdexcept>
#include <utility>
#include <vector>

// PRODUCTION_DEFINES
#define MESH_DEBUG_PRINTLN(...) ((void)0)
void delay(int) {}
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}

class Ina228Driver {
public:
uint16_t control = 0;
uint16_t flags = 0;
unsigned writes = 0;
unsigned staleReads = 0;
void enableAlert(bool enable_uvlo, bool active_high, bool latch_alert);
bool writeRegister16(uint8_t reg, uint16_t value) {
check(reg == INA228_REG_DIAG_ALRT, "unexpected register write");
++writes;
control = value;
return true;
}
uint16_t readRegister16(uint8_t reg) {
check(reg == INA228_REG_DIAG_ALRT, "unexpected register read");
if (staleReads) {
--staleReads;
return (control & ~INA228_DIAG_ALRT_SLOWALERT) | flags;
}
return control | flags;
}
};

// PRODUCTION_METHOD

// INA228 datasheet Table 7-16: SLOWALERT selects raw or averaged comparison.
bool underVoltage(uint16_t diag, unsigned rawMv, unsigned averagedMv) {
return ((diag & 0x2000) ? averagedMv : rawMv) < 2500;
}

int main() {
const std::vector<std::pair<const char*, std::function<void()>>> tests = {
{"UVLO uses averaging with latch and active-low polarity", [] {
Ina228Driver ina;
ina.enableAlert(true, false, true);
check(ina.control == 0xa000, "expected SLOWALERT + ALATCH (0xa000)");
check(ina.writes == 1, "successful configuration unexpectedly retried");
}},
{"latch and polarity remain independent of averaging", [] {
Ina228Driver ina;
ina.enableAlert(true, false, false);
check(ina.control == 0x2000, "transparent active-low should retain averaging");
ina.enableAlert(true, true, true);
check(ina.control == 0xb000, "active-high should retain latch and averaging");
}},
{"disarming clears averaging and latch", [] {
Ina228Driver ina;
ina.enableAlert(true, false, true);
ina.enableAlert(false, false, false);
check(ina.control == 0, "disarm retained configuration bits");
ina.enableAlert(false, true, true);
check(ina.control == 0x9000, "disabled UVLO changed requested latch or polarity");
}},
{"readback retries if SLOWALERT did not stick", [] {
Ina228Driver ina;
ina.staleReads = 1;
ina.enableAlert(true, false, true);
check(ina.writes == 2, "missing SLOWALERT was accepted during readback");
check(ina.control == 0xa000, "retry lost averaging");
}},
{"changing status flags do not trigger configuration retries", [] {
Ina228Driver ina;
ina.flags = 0x0fff;
ina.enableAlert(true, false, true);
check(ina.writes == 1, "status flags affected configuration verification");
}},
{"persistent configuration mismatch retains bounded retries", [] {
Ina228Driver ina;
ina.staleReads = 10;
ina.enableAlert(true, false, true);
check(ina.writes == 3, "configuration retry limit changed");
}},
{"raw voltage dip is filtered; sustained undervoltage still alerts", [] {
Ina228Driver ina;
ina.enableAlert(true, false, true);
check(!underVoltage(ina.control, 2400, 3250), "brief raw dip triggered UVLO");
check(underVoltage(ina.control, 2400, 2440), "real averaged undervoltage missed");
check(!underVoltage(ina.control, 2400, 2500), "threshold equality triggered UVLO");
check(underVoltage(0x8000, 2400, 3250), "unfiltered negative control did not alert");
}}
};
unsigned failures = 0;
for (const auto& test : tests) {
try {
test.second();
std::cout << "PASS " << test.first << '\n';
} catch (const std::exception& error) {
++failures;
std::cerr << "FAIL " << test.first << ": " << error.what() << '\n';
}
}
std::cout << tests.size() - failures << '/' << tests.size() << " passed\n";
return failures ? 1 : 0;
}
41 changes: 41 additions & 0 deletions test/mr2_ina_alert/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Host regression for the production INA228 alert configuration function.

Run with Python 3; requires g++ on PATH or CXX pointing to a compiler.
Only register I/O is mocked. The final comparison check models datasheet
Table 7-16, not a physical ADC or a board timing measurement.
"""
from pathlib import Path
import os
import re
import shutil
import subprocess
import tempfile

ROOT = Path(__file__).resolve().parents[2]
DRIVER = ROOT / "variants/inhero_mr2/lib/Ina228Driver.cpp"
HEADER = DRIVER.with_suffix(".h")
source = DRIVER.read_text(encoding="utf-8")
start = source.index("void Ina228Driver::enableAlert(")
brace = source.index("{", start)
depth = 0
for end in range(brace, len(source)):
depth += (source[end] == "{") - (source[end] == "}")
if depth == 0:
method = source[start:end + 1]
break
else:
raise ValueError("Unbalanced enableAlert function")
defines = "\n".join(re.findall(r"^#define INA228_(?:DIAG_ALRT_|REG_).*$", HEADER.read_text(encoding="utf-8"), re.M))
fixture = Path(__file__).with_name("fixture.inc").read_text(encoding="utf-8")
code = fixture.replace("// PRODUCTION_DEFINES", defines).replace("// PRODUCTION_METHOD", method)
compiler = os.environ.get("CXX") or shutil.which("g++")
if not compiler and Path("C:/Tools/mingw64/bin/g++.exe").is_file():
compiler = "C:/Tools/mingw64/bin/g++.exe"
if not compiler:
raise SystemExit("Set CXX to a C++17 compiler or add g++ to PATH")
with tempfile.TemporaryDirectory(prefix="mr2-ina-alert-") as temp:
cpp = Path(temp) / "test.cpp"
exe = Path(temp) / ("test.exe" if os.name == "nt" else "test")
cpp.write_text(code, encoding="utf-8")
subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror", str(cpp), "-o", str(exe)], check=True)
subprocess.run([str(exe)], check=True)
Loading