initial commit
This commit is contained in:
commit
5ae3ffcc8a
12 changed files with 1014 additions and 0 deletions
12
.clangd
Normal file
12
.clangd
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
CompileFlags:
|
||||||
|
Remove:
|
||||||
|
# GCC-specific flags that clangd doesn't understand
|
||||||
|
- -fstrict-volatile-bitfields
|
||||||
|
- -fno-tree-switch-conversion
|
||||||
|
- -fno-jump-tables
|
||||||
|
- -freorder-blocks
|
||||||
|
# Xtensa-specific (esp32dev)
|
||||||
|
- -mlongcalls
|
||||||
|
- -mtext-section-literals
|
||||||
|
- -fipa-pta
|
||||||
|
- -free
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
.pio
|
||||||
|
.cache
|
||||||
362
compile_commands.json
Normal file
362
compile_commands.json
Normal file
File diff suppressed because one or more lines are too long
37
include/README
Normal file
37
include/README
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
24
include/screen.h
Normal file
24
include/screen.h
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
#include <Adafruit_SSD1306.h>
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 128
|
||||||
|
#define SCREEN_HEIGHT 64
|
||||||
|
|
||||||
|
#define OLED_RESET -1 // Not used
|
||||||
|
#define SCREEN_ADDRESS 0x3C // < from Datasheet, maybe 0x3c, maybe 0x3d
|
||||||
|
|
||||||
|
#define I2C_SDA_PIN 6 // D4 (GPIO 06) on XIAO_ESP32_C3
|
||||||
|
#define I2C_SCL_PIN 7 // D5 (GPIO 07) on XIAO_ESP32_C3
|
||||||
|
|
||||||
|
namespace Screen {
|
||||||
|
|
||||||
|
extern Adafruit_SSD1306 display;
|
||||||
|
|
||||||
|
// attempt to start up screen; will lock program if fails
|
||||||
|
void setup_screen();
|
||||||
|
|
||||||
|
// example code to draw to screen (test working)
|
||||||
|
void test_fillrect();
|
||||||
|
|
||||||
|
void wire_search(uint8_t loops);
|
||||||
|
} // namespace Screen
|
||||||
46
lib/README
Normal file
46
lib/README
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
269
lib/SerialCmd/SerialCmd.cpp
Normal file
269
lib/SerialCmd/SerialCmd.cpp
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
#include "SerialCmd.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// --- CmdArgs ---
|
||||||
|
|
||||||
|
void CmdArgs::parse(char* line) {
|
||||||
|
_command = nullptr;
|
||||||
|
_rawCount = 0;
|
||||||
|
_posCount = 0;
|
||||||
|
|
||||||
|
// In-place tokenization with quoted string support
|
||||||
|
bool inToken = false;
|
||||||
|
bool inQuote = false;
|
||||||
|
char* tokenStart = nullptr;
|
||||||
|
|
||||||
|
for (char* p = line; ; ++p) {
|
||||||
|
char c = *p;
|
||||||
|
|
||||||
|
if (c == '\0') {
|
||||||
|
if (inToken && _rawCount < SERIAL_CMD_MAX_ARGS) {
|
||||||
|
_tokens[_rawCount++] = tokenStart;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inQuote) {
|
||||||
|
if (c == '"') {
|
||||||
|
*p = '\0';
|
||||||
|
inQuote = false;
|
||||||
|
inToken = false;
|
||||||
|
if (_rawCount < SERIAL_CMD_MAX_ARGS) {
|
||||||
|
_tokens[_rawCount++] = tokenStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '"') {
|
||||||
|
if (inToken && _rawCount < SERIAL_CMD_MAX_ARGS) {
|
||||||
|
*p = '\0';
|
||||||
|
_tokens[_rawCount++] = tokenStart;
|
||||||
|
}
|
||||||
|
inQuote = true;
|
||||||
|
inToken = true;
|
||||||
|
tokenStart = p + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ' ' || c == '\t') {
|
||||||
|
if (inToken) {
|
||||||
|
*p = '\0';
|
||||||
|
if (_rawCount < SERIAL_CMD_MAX_ARGS) {
|
||||||
|
_tokens[_rawCount++] = tokenStart;
|
||||||
|
}
|
||||||
|
inToken = false;
|
||||||
|
}
|
||||||
|
} else if (!inToken) {
|
||||||
|
inToken = true;
|
||||||
|
tokenStart = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First token is the command name
|
||||||
|
if (_rawCount > 0) {
|
||||||
|
_command = _tokens[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classify remaining tokens: flags vs positional
|
||||||
|
for (int i = 1; i < _rawCount; ++i) {
|
||||||
|
if (_tokens[i][0] == '-') {
|
||||||
|
// It's a flag — skip for positional list
|
||||||
|
} else {
|
||||||
|
if (_posCount < SERIAL_CMD_MAX_ARGS) {
|
||||||
|
_positional[_posCount++] = _tokens[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* CmdArgs::arg(int index) const {
|
||||||
|
if (index < 0 || index >= _posCount) return nullptr;
|
||||||
|
return _positional[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int CmdArgs::intArg(int index, int fallback) const {
|
||||||
|
const char* v = arg(index);
|
||||||
|
return v ? atoi(v) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CmdArgs::floatArg(int index, float fallback) const {
|
||||||
|
const char* v = arg(index);
|
||||||
|
return v ? atof(v) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CmdArgs::has(const char* flag) const {
|
||||||
|
for (int i = 1; i < _rawCount; ++i) {
|
||||||
|
if (strcmp(_tokens[i], flag) == 0) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* CmdArgs::raw(int index) const {
|
||||||
|
if (index < 0 || index >= _rawCount) return nullptr;
|
||||||
|
return _tokens[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- SerialCmd ---
|
||||||
|
|
||||||
|
SerialCmd::SerialCmd(Stream& stream) : _stream(stream) {
|
||||||
|
_line[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialCmd::on(const char* name, Handler handler) {
|
||||||
|
return on(name, nullptr, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialCmd::on(const char* name, const char* usage, Handler handler) {
|
||||||
|
if (_commandCount >= SERIAL_CMD_MAX_COMMANDS) return false;
|
||||||
|
_commands[_commandCount++] = { name, usage, handler };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialCmd::onDefault(Handler handler) {
|
||||||
|
_defaultHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialCmd::poll() {
|
||||||
|
while (_stream.available()) {
|
||||||
|
feedChar(_stream.read());
|
||||||
|
if (_lineReady) {
|
||||||
|
dispatch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialCmd::wait() {
|
||||||
|
_lineReady = false;
|
||||||
|
while (!_lineReady) {
|
||||||
|
if (_stream.available()) {
|
||||||
|
feedChar(_stream.read());
|
||||||
|
} else {
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialCmd::feedChar(char c) {
|
||||||
|
// Handle \r\n sequences — ignore \n right after \r
|
||||||
|
if (c == '\n' && _prevCR) {
|
||||||
|
_prevCR = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_prevCR = (c == '\r');
|
||||||
|
|
||||||
|
// End of line
|
||||||
|
if (c == '\r' || c == '\n') {
|
||||||
|
if (_echo) _stream.println();
|
||||||
|
_line[_linePos] = '\0';
|
||||||
|
if (_linePos > 0) {
|
||||||
|
_lineReady = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backspace (BS or DEL)
|
||||||
|
if (c == '\b' || c == 127) {
|
||||||
|
if (_linePos > 0) {
|
||||||
|
_linePos--;
|
||||||
|
if (_echo) _stream.print("\b \b");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate printable characters
|
||||||
|
if (_linePos < SERIAL_CMD_MAX_LINE - 1) {
|
||||||
|
_line[_linePos++] = c;
|
||||||
|
if (_echo) _stream.print(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialCmd::dispatch() {
|
||||||
|
_lineReady = false;
|
||||||
|
|
||||||
|
CmdArgs args;
|
||||||
|
args.parse(_line);
|
||||||
|
|
||||||
|
if (!args.command()) {
|
||||||
|
_linePos = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Built-in /sync — checked before user commands
|
||||||
|
if (strcmp(args.command(), "/sync") == 0) {
|
||||||
|
sendSyncList();
|
||||||
|
_linePos = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _commandCount; ++i) {
|
||||||
|
if (strcmp(_commands[i].name, args.command()) == 0) {
|
||||||
|
_commands[i].handler(args);
|
||||||
|
_linePos = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_defaultHandler) {
|
||||||
|
_defaultHandler(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
_linePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialCmd::sendSyncList() {
|
||||||
|
_stream.println("#sync-begin");
|
||||||
|
for (int i = 0; i < _commandCount; ++i) {
|
||||||
|
_stream.print(_commands[i].name);
|
||||||
|
if (_commands[i].usage) {
|
||||||
|
_stream.print(' ');
|
||||||
|
_stream.print(_commands[i].usage);
|
||||||
|
}
|
||||||
|
_stream.println();
|
||||||
|
}
|
||||||
|
_stream.println("#sync-end");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialCmd::sync(unsigned long timeout_ms) {
|
||||||
|
sendSyncList();
|
||||||
|
|
||||||
|
unsigned long deadline = millis() + timeout_ms;
|
||||||
|
char buf[SERIAL_CMD_MAX_LINE];
|
||||||
|
int pos = 0;
|
||||||
|
bool prevCR = false;
|
||||||
|
|
||||||
|
while (millis() < deadline) {
|
||||||
|
if (!_stream.available()) {
|
||||||
|
yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = _stream.read();
|
||||||
|
|
||||||
|
// Handle \r\n sequences
|
||||||
|
if (c == '\n' && prevCR) {
|
||||||
|
prevCR = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
prevCR = (c == '\r');
|
||||||
|
|
||||||
|
if (c == '\r' || c == '\n') {
|
||||||
|
buf[pos] = '\0';
|
||||||
|
if (pos > 0 && strcmp(buf, "#acknowledge-sync") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pos = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos < SERIAL_CMD_MAX_LINE - 1) {
|
||||||
|
buf[pos++] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
87
lib/SerialCmd/SerialCmd.h
Normal file
87
lib/SerialCmd/SerialCmd.h
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#ifndef SERIAL_CMD_H
|
||||||
|
#define SERIAL_CMD_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#ifndef SERIAL_CMD_MAX_LINE
|
||||||
|
#define SERIAL_CMD_MAX_LINE 128
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SERIAL_CMD_MAX_ARGS
|
||||||
|
#define SERIAL_CMD_MAX_ARGS 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SERIAL_CMD_MAX_COMMANDS
|
||||||
|
#define SERIAL_CMD_MAX_COMMANDS 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class CmdArgs {
|
||||||
|
public:
|
||||||
|
void parse(char* line);
|
||||||
|
|
||||||
|
const char* command() const { return _command; }
|
||||||
|
|
||||||
|
// Positional args (skips flags)
|
||||||
|
const char* arg(int index) const;
|
||||||
|
int intArg(int index, int fallback = 0) const;
|
||||||
|
float floatArg(int index, float fallback = 0.0f) const;
|
||||||
|
int count() const { return _posCount; }
|
||||||
|
|
||||||
|
// Flag check
|
||||||
|
bool has(const char* flag) const;
|
||||||
|
|
||||||
|
// Raw token access (includes flags)
|
||||||
|
const char* raw(int index) const;
|
||||||
|
int rawCount() const { return _rawCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* _command = nullptr;
|
||||||
|
const char* _tokens[SERIAL_CMD_MAX_ARGS];
|
||||||
|
int _rawCount = 0;
|
||||||
|
|
||||||
|
const char* _positional[SERIAL_CMD_MAX_ARGS];
|
||||||
|
int _posCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SerialCmd {
|
||||||
|
public:
|
||||||
|
using Handler = std::function<void(CmdArgs&)>;
|
||||||
|
|
||||||
|
explicit SerialCmd(Stream& stream);
|
||||||
|
|
||||||
|
bool on(const char* name, Handler handler);
|
||||||
|
bool on(const char* name, const char* usage, Handler handler);
|
||||||
|
void onDefault(Handler handler);
|
||||||
|
|
||||||
|
bool poll();
|
||||||
|
void wait();
|
||||||
|
bool sync(unsigned long timeout_ms = 5000);
|
||||||
|
|
||||||
|
void setEcho(bool enabled) { _echo = enabled; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void feedChar(char c);
|
||||||
|
void dispatch();
|
||||||
|
void sendSyncList();
|
||||||
|
|
||||||
|
Stream& _stream;
|
||||||
|
bool _echo = false;
|
||||||
|
|
||||||
|
char _line[SERIAL_CMD_MAX_LINE];
|
||||||
|
int _linePos = 0;
|
||||||
|
bool _lineReady = false;
|
||||||
|
bool _prevCR = false;
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
const char* name;
|
||||||
|
const char* usage;
|
||||||
|
Handler handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
Command _commands[SERIAL_CMD_MAX_COMMANDS];
|
||||||
|
int _commandCount = 0;
|
||||||
|
Handler _defaultHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
19
platformio.ini
Normal file
19
platformio.ini
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:esp32c3]
|
||||||
|
platform = espressif32
|
||||||
|
board = seeed_xiao_esp32c3
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
monitor_port = /dev/ttyACM2
|
||||||
|
lib_deps =
|
||||||
|
adafruit/Adafruit SSD1306
|
||||||
|
adafruit/Adafruit GFX Library
|
||||||
65
src/main.cpp
Normal file
65
src/main.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SerialCmd.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <screen.h>
|
||||||
|
|
||||||
|
#define BAUD 115200
|
||||||
|
|
||||||
|
SerialCmd cmd(Serial);
|
||||||
|
|
||||||
|
unsigned long lastStatus = 0;
|
||||||
|
|
||||||
|
void register_commands() {
|
||||||
|
cmd.on("/ping", "— responds with pong",
|
||||||
|
[](CmdArgs &args) { Serial.println("pong"); });
|
||||||
|
|
||||||
|
cmd.on("/screen", "'-draw' to redraw rectangles", [](CmdArgs &args) {
|
||||||
|
if (args.has("-draw")) {
|
||||||
|
Screen::test_fillrect();
|
||||||
|
Serial.println("Completed drawing");
|
||||||
|
} else {
|
||||||
|
Serial.println("usage: /screen -draw");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.on("/status", "— print device status", [](CmdArgs &args) {
|
||||||
|
Serial.printf("uptime: %lus\n", millis() / 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.on("/scanI2C", "-scans for any recognizable I2C devices",
|
||||||
|
[](CmdArgs &args) { Screen::wire_search(3); });
|
||||||
|
|
||||||
|
cmd.onDefault(
|
||||||
|
[](CmdArgs &args) { Serial.printf("unknown: %s\n", args.command()); });
|
||||||
|
|
||||||
|
Serial.println("ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(BAUD);
|
||||||
|
while (!Serial)
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
Serial.printf("Serial communication initialized at %d\n", BAUD);
|
||||||
|
|
||||||
|
Serial.println("10s delay before I2C initialization");
|
||||||
|
delay(10000);
|
||||||
|
Serial.println("Delay Complete");
|
||||||
|
|
||||||
|
Screen::setup_screen();
|
||||||
|
|
||||||
|
Screen::test_fillrect();
|
||||||
|
|
||||||
|
register_commands();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
cmd.poll();
|
||||||
|
|
||||||
|
// Periodic status to prove non-blocking
|
||||||
|
if (millis() - lastStatus >= 10000) {
|
||||||
|
lastStatus = millis();
|
||||||
|
Serial.printf("[uptime %lus]\n", millis() / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/screen.cpp
Normal file
80
src/screen.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#include "screen.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Screen {
|
||||||
|
|
||||||
|
bool initialized_wire = false;
|
||||||
|
bool initialized_screen = false;
|
||||||
|
|
||||||
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
|
||||||
|
|
||||||
|
// attempt to start up screen; will lock program if fails
|
||||||
|
void setup_screen() {
|
||||||
|
if (!initialized_wire) {
|
||||||
|
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
|
||||||
|
initialized_wire = true;
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
||||||
|
Serial.println("SSD1306 failed initiation");
|
||||||
|
delay(500);
|
||||||
|
} else {
|
||||||
|
initialized_screen = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// example code to draw to screen (test working)
|
||||||
|
void test_fillrect() {
|
||||||
|
display.clearDisplay();
|
||||||
|
|
||||||
|
for (int16_t i = 0; i < display.height() / 2; i += 3) {
|
||||||
|
display.fillRect(i, i, display.width() - i * 2, display.height() - i * 2,
|
||||||
|
SSD1306_INVERSE);
|
||||||
|
display.display();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wire_search(uint8_t loops = 5) {
|
||||||
|
if (!initialized_wire) {
|
||||||
|
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
|
||||||
|
initialized_wire = true;
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < loops; i++) {
|
||||||
|
byte error, address;
|
||||||
|
int nDevices;
|
||||||
|
Serial.println("Scanning...");
|
||||||
|
|
||||||
|
nDevices = 0;
|
||||||
|
for (address = 1; address < 127; address++) {
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
error = Wire.endTransmission();
|
||||||
|
|
||||||
|
if (error == 0) {
|
||||||
|
Serial.print("I2C device found at address 0x");
|
||||||
|
if (address < 16)
|
||||||
|
Serial.print("0");
|
||||||
|
Serial.print(address, HEX);
|
||||||
|
Serial.println(" !");
|
||||||
|
|
||||||
|
nDevices++;
|
||||||
|
} else if (error == 4) {
|
||||||
|
Serial.print("Unknown error at address 0x");
|
||||||
|
if (address < 16)
|
||||||
|
Serial.print("0");
|
||||||
|
Serial.println(address, HEX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nDevices == 0)
|
||||||
|
Serial.println("No I2C devices found.");
|
||||||
|
else
|
||||||
|
Serial.println("Done.");
|
||||||
|
delay(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Screen
|
||||||
11
test/README
Normal file
11
test/README
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
Loading…
Reference in a new issue