Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
BasedOnStyle: LLVM
IndentWidth: 2
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: Empty
AllowShortFunctionsOnASingleLine: All
BreakBeforeBraces: Attach
AllowShortBlocksOnASingleLine: Always
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
Expand Down
6 changes: 1 addition & 5 deletions Spresense-CycleComputer.ino
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#include <LowPower.h>

#include "src/App.h"

App app;
Expand All @@ -10,6 +8,4 @@ void setup() {
app.begin();
}

void loop() {
app.update();
}
void loop() { app.update(); }
62 changes: 0 additions & 62 deletions docs/requirements.md

This file was deleted.

137 changes: 96 additions & 41 deletions src/App.h
Original file line number Diff line number Diff line change
@@ -1,67 +1,122 @@
#pragma once

#include "domain/Clock.h"
#include "domain/Trip.h"
#include "hardware/Gnss.h"
#include "hardware/OLED.h"
#include "ui/Frame.h"
#include "Config.h"
#include "domain/BatteryMonitor.h"
#include "domain/DataStore.h"
#include "domain/TripData.h"
#include "ui/DisplayFrame.h"
#include "ui/Input.h"
#include "ui/Mode.h"
#include "ui/Renderer.h"
#include <GNSS.h>
#include <LowPower.h>

template <typename T> struct DoubleBuffer {
T buffers[2];
int index = 0;
T &current() { return buffers[index]; }
void initialize(const T &value) { buffers[0] = buffers[1] = value; }
bool apply(const T &newValue) {
index = 1 - index;
buffers[index] = newValue;
return buffers[index] != buffers[1 - index];
}
};

class App {
private:
OLED oled;
Input input;
Gnss gnss;
Mode mode;
Trip trip;
Clock clock;
Renderer renderer;
SpGnss gnss;
DataStore store;
BatteryMonitor batteryMonitor;
Input input;
Renderer renderer;
Mode mode = Mode::SPD_TIM;
DoubleBuffer<TripData> trip;
DoubleBuffer<DisplayFrame> frame;
DoubleBuffer<SaveData> save;
unsigned long now = 0, lastUi = 0, lastSave = 0;
GnssData curGnss = {};
Input::Event curBtn = Input::Event::NONE;

public:
App() : input(Config::Pins::BUTTON_SELECT, Config::Pins::BUTTON_PAUSE) {}

void begin() {
oled.begin();
Serial.begin(115200);
bool gnssInitialized = (gnss.begin() == 0);
if (gnssInitialized) {
gnss.select(GPS);
gnss.select(GLONASS);
gnss.select(QZ_L1CA);
gnssInitialized = (gnss.start(COLD_START) == 0);
}

if (!renderer.begin() || !gnssInitialized) {
LowPower.begin();
LowPower.deepSleep(0);
}

input.begin();
gnss.begin();
trip.begin();
batteryMonitor.begin();
SaveData savedData = store.load();
trip.initialize(savedData.toTripData());
trip.current().clock.begin();
save.initialize(savedData);
}

void update() {
handleInput();
static unsigned long nextLoop = millis();
while (millis() < nextLoop) delay(1);
nextLoop += 33;

gnss.update();
const SpNavData &navData = gnss.getNavData();
now = millis();
curBtn = input.update();
curGnss.updated = (gnss.waitUpdate(0) == 1);
if (curGnss.updated) gnss.getNavData(&curGnss.navData);

trip.update(navData, millis());
clock.update(navData);
if (curBtn != Input::Event::NONE) handleButton();
trip.apply(TripData(trip.current(), curGnss, now));

Frame frame(trip, clock, mode.get(), (SpFixMode)navData.posFixMode);
renderer.render(oled, frame);
if (curBtn != Input::Event::NONE || now - lastUi >= Config::UI::UPDATE_INTERVAL_MS) {
if (frame.apply(DisplayFrame(trip.current(), curGnss, trip.current().clock.now(), mode))) {
renderer.render(frame.current());
lastUi = now;
}
}

if (now - lastSave >= DataStore::SAVE_INTERVAL_MS && !curGnss.updated) {
if (save.apply(SaveData(trip.current(), batteryMonitor.update()))) store.save(save.current());
lastSave = now;
}
}

private:
void handleInput() {
switch (input.update()) {
case Input::ID::SELECT:
mode.next();
void handleButton() {
auto &tripState = trip.current();

switch (curBtn) {
case Input::Event::SELECT:
mode = static_cast<Mode>((static_cast<int>(mode) + 1) % 3);
return;
case Input::ID::PAUSE:
trip.pause();

case Input::Event::PAUSE:
tripState.togglePause();
return;
case Input::ID::RESET:
switch (mode.get()) {
case Mode::ID::SPD_TIME:
trip.resetTime();
break;
case Mode::ID::AVG_ODO:
trip.resetOdometerAndMovingTime();
break;
default:
break;
}

case Input::Event::RESET:
if (mode == Mode::SPD_TIM) tripState.clearAvgOdo();
if (mode == Mode::MAX_CLK) tripState.clearMaxSpeed();
if (mode == Mode::AVG_ODO) tripState.clearAllData();
return;

case Input::Event::RESET_LONG:
tripState.clearAllData();
store.clear();
renderer.resetDisplay();
frame.initialize({});
save.initialize(SaveData(tripState, 0));
return;
case Input::ID::NONE:

default:
return;
}
}
Expand Down
99 changes: 53 additions & 46 deletions src/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,60 @@

namespace Config {

constexpr unsigned long DEBOUNCE_DELAY_MS = 50;
constexpr unsigned long DISPLAY_UPDATE_INTERVAL_MS = 100;

// ハードウェアピン設定
namespace Pins {
constexpr int BUTTON_SELECT = PIN_D09; // モード切替ボタン
constexpr int BUTTON_PAUSE = PIN_D04; // 一時停止ボタン
constexpr int VOLTAGE_SENSE = PIN_A5; // バッテリー電圧監視用ADC
constexpr int LOW_BATT_LED = PIN_D00; // 低電圧警告LED
} // namespace Pins

// OLED ディスプレイ設定
namespace Display {
constexpr int WIDTH = 128; // OLED横幅 (ピクセル)
constexpr int HEIGHT = 64; // OLED縦幅 (ピクセル)
constexpr int ADDRESS = 0x3C; // I2Cアドレス (SSD1306標準)
} // namespace Display

// ボタン入力設定
namespace Button {
constexpr unsigned long DEBOUNCE_MS = 20; // チャタリング防止時間
constexpr unsigned long SINGLE_PRESS_MS = 30; // シングルプレス判定時間
constexpr unsigned long LONG_PRESS_MS = 3000; // 長押し判定時間
} // namespace Button

// GNSS設定
namespace Gnss {
constexpr unsigned long SIGNAL_TIMEOUT_MS = 3000; // GNSS信号ロスト判定時間
constexpr float MIN_MOVING_SPEED_KMH = 4.0f; // 移動判定の最低速度(GPSドリフト対策)
constexpr float SPEED_SMOOTHING = 0.7f; // EMA平滑化係数 (0.0-1.0、小さいほど滑らか)
} // namespace Gnss

// データ保存設定
namespace Storage {
constexpr unsigned long SAVE_INTERVAL_MS = 30000; // 自動保存間隔 (30秒)
constexpr unsigned long EEPROM_ADDR = 0; // EEPROM保存先アドレス
constexpr float MAX_VALID_DISTANCE_KM = 1000000.0f; // 距離データ有効範囲
} // namespace Storage

// 電圧監視設定
namespace Voltage {
constexpr float LOW_THRESHOLD = 1.0f; // 低電圧警告閾値 (V)
constexpr float REFERENCE_VOLTAGE = 3.3f; // ADC基準電圧 (V)
constexpr float ADC_MAX_VALUE = 1023.0f; // ADC最大値 (10bit)
} // namespace Voltage

// UI更新設定
namespace UI {
constexpr unsigned long UPDATE_INTERVAL_MS = 500; // UI更新間隔
constexpr unsigned long BLINK_INTERVAL_MS = 500; // 点滅間隔
constexpr int MODE_COUNT = 3; // 表示モード数
} // namespace UI

// 時刻設定
namespace Time {

constexpr int JST_OFFSET = 9;
constexpr int VALID_YEAR_START = 2025;

constexpr int TIMEZONE_OFFSET_HOURS = 9; // JST = UTC + 9
constexpr int MIN_VALID_YEAR = 2026; // GPS時刻の有効判定年
} // namespace Time

namespace Pin {

constexpr int BTN_A = PIN_D09;
constexpr int BTN_B = PIN_D04;
constexpr int WARN_LED = PIN_D00;

} // namespace Pin

namespace OLED {

constexpr int WIDTH = 128;
constexpr int HEIGHT = 64;
constexpr int ADDRESS = 0x3C;

} // namespace OLED

namespace Renderer {

constexpr int16_t HEADER_HEIGHT = 12;
constexpr int16_t HEADER_TEXT_SIZE = 1;

} // namespace Renderer

constexpr float MIN_MOVING_SPEED_KMH = 0.001f;

namespace Odometer {

constexpr float MIN_ABS = 1e-6f;
constexpr float MIN_DELTA = 0.002f;
constexpr float MAX_DELTA = 1.0f;

} // namespace Odometer

namespace Input {

constexpr unsigned long SIMULTANEOUS_DELAY_MS = 50;

} // namespace Input

} // namespace Config
18 changes: 18 additions & 0 deletions src/domain/BatteryMonitor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "../Config.h"
#include <Arduino.h>

class BatteryMonitor {
public:
void begin() { pinMode(Config::Pins::LOW_BATT_LED, OUTPUT); }

float update() {
const int rawValue = analogRead(Config::Pins::VOLTAGE_SENSE);
const float voltageRatio = rawValue / Config::Voltage::ADC_MAX_VALUE;
const float voltage = voltageRatio * Config::Voltage::REFERENCE_VOLTAGE;
const bool exceedsThreshold = Config::Voltage::REFERENCE_VOLTAGE < voltage;
digitalWrite(Config::Pins::LOW_BATT_LED, exceedsThreshold ? LOW : HIGH);
return voltage;
}
};
Loading
Loading