readmes and gps changes
This commit is contained in:
parent
a5e100952c
commit
b41050c2c6
21
README.md
21
README.md
@ -1,5 +1,22 @@
|
|||||||
# SkyLok
|
# SkyLok
|
||||||
|
|
||||||
|
Pi Zero W 2 captures frames, runs a motion shader, sends diffs over LoRa.
|
||||||
|
|
||||||
|
- `camera_example/` — libcamera + libjpeg-turbo, streams MJPEG over TCP. Works.
|
||||||
|
- `shader_example/` — offscreen GL motion diff between two frames. Works on desktop.
|
||||||
|
- `math_example/` — pixel → 3D vertex projection (depth map to OBJ mesh).
|
||||||
|
- `chip_test_example/` — LR1121 LoRa + ICM-20948 IMU + u-blox GPS drivers/tests.
|
||||||
|
- `main.cpp` — prototype that wires camera + shader + TCP together.
|
||||||
|
|
||||||
|
References:
|
||||||
- https://www.youtube.com/watch?v=zFiubdrJqqI
|
- https://www.youtube.com/watch?v=zFiubdrJqqI
|
||||||
- Client: libcamera::DirectLowResVid -> EGL::MotionShader -> MATH::Vortexes -> SPI/RadioHead::LoRa -> Server
|
- https://github.com/ConsistentlyInconsistentYT/Pixeltovoxelprojector/
|
||||||
- Server: eats all incoming and cooks
|
|
||||||
|
## Build main.cpp
|
||||||
|
|
||||||
|
```sh
|
||||||
|
g++ main.cpp -o main \
|
||||||
|
$(pkg-config --cflags --libs libcamera libevent) \
|
||||||
|
-levent -levent_pthreads -lpthread \
|
||||||
|
-lturbojpeg -lglfw -lGLEW -lGL -lEGL -lGLESv2
|
||||||
|
```
|
||||||
|
|||||||
@ -1,27 +1,29 @@
|
|||||||
# camera_example
|
# camera_example
|
||||||
|
|
||||||
libcamera viewfinder (XRGB8888) -> libjpeg-turbo -> TCP. One client at a time.
|
libcamera viewfinder (XRGB8888) → libjpeg-turbo → TCP. One client at a time.
|
||||||
|
|
||||||
Wire format: `[uint32 BE jpeg_size][jpeg_bytes]` repeating.
|
Wire format: `[uint32 BE size][jpeg_bytes]` repeating.
|
||||||
|
|
||||||
Knobs are constants at the top of `camera.cpp` (fps, quality, AF mode, lens
|
Knobs (fps, quality, AF, lens, port, resolution) are `constexpr` at the top of `camera.cpp`.
|
||||||
position, port, resolution).
|
|
||||||
|
|
||||||
## Build on the Pi
|
## Build on the Pi
|
||||||
|
|
||||||
apt install g++ pkg-config libcamera-dev libevent-dev libjpeg-turbo8-dev
|
```sh
|
||||||
make
|
apt install g++ pkg-config libcamera-dev libevent-dev libjpeg-turbo8-dev
|
||||||
|
make
|
||||||
## Cross-build (x86 -> armv6l) on a Nix host
|
```
|
||||||
|
|
||||||
nix build
|
|
||||||
|
|
||||||
The resulting binary links against `/nix/store`, so it only runs on a Pi that
|
|
||||||
has those libs available (i.e. nix installed). For a vanilla Pi OS target,
|
|
||||||
use the Makefile on the device, or `./deploy.sh user@host` to rsync+build.
|
|
||||||
|
|
||||||
## Receive
|
## Receive
|
||||||
|
|
||||||
Any TCP client that reads the framed JPEGs. Quick check:
|
```sh
|
||||||
|
nc <pi-ip> 5000 | ffplay -f mjpeg -
|
||||||
|
```
|
||||||
|
|
||||||
nc <pi-ip> 5000 | ffplay -f mjpeg -
|
## Cross-build (Nix, x86 → armv6l)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix build
|
||||||
|
```
|
||||||
|
|
||||||
|
Binary links against `/nix/store` so it only runs on a Pi with Nix installed.
|
||||||
|
For plain Pi OS: use `make` on the device, or `./deploy.sh user@host` to rsync+build.
|
||||||
|
|||||||
120
chip_test_example/README.md
Normal file
120
chip_test_example/README.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# chip_test_example
|
||||||
|
|
||||||
|
Tests for LR1121 LoRa, ICM-20948 IMU, u-blox GPS on Raspberry Pi Zero W 2.
|
||||||
|
Header-only drivers, kernel ioctls, no external libs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
|
||||||
|
### LR1121 (SPI0)
|
||||||
|
|
||||||
|
| Module | Pi GPIO | Pi pin |
|
||||||
|
|--------|---------|--------|
|
||||||
|
| SCK | GPIO11 | 23 |
|
||||||
|
| MOSI | GPIO10 | 19 |
|
||||||
|
| MISO | GPIO9 | 21 |
|
||||||
|
| NSS | GPIO8 | 24 |
|
||||||
|
| BUSY | GPIO24 | 18 |
|
||||||
|
| NRESET | GPIO25 | 22 |
|
||||||
|
| DIO9 | GPIO4 | 7 |
|
||||||
|
| DIO8 | GPIO23 | 16 |
|
||||||
|
|
||||||
|
Enable: `sudo raspi-config` → Interfaces → SPI → Yes → reboot
|
||||||
|
|
||||||
|
### ICM-20948 (I2C1)
|
||||||
|
|
||||||
|
| Module | Pi GPIO | Pi pin |
|
||||||
|
|--------|---------|--------|
|
||||||
|
| SDA | GPIO2 | 3 |
|
||||||
|
| SCL | GPIO3 | 5 |
|
||||||
|
| AD0 | GND → addr 0x68 / VCC → addr 0x69 |
|
||||||
|
|
||||||
|
Enable: `sudo raspi-config` → Interfaces → I2C → Yes → reboot
|
||||||
|
|
||||||
|
### u-blox GPS (UART0)
|
||||||
|
|
||||||
|
| Module | Pi GPIO | Pi pin |
|
||||||
|
|--------|---------|--------|
|
||||||
|
| TX | GPIO15 | 10 |
|
||||||
|
| RX | GPIO14 | 8 |
|
||||||
|
|
||||||
|
Enable: `sudo raspi-config` → Interfaces → Serial Port → login shell: No, hardware: Yes → reboot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install g++ make
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Run verbose first so you see exactly where it fails:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ./lora_rx -v --433
|
||||||
|
sudo ./imu_test -v
|
||||||
|
./gps_test -v
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LoRa debug
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls /dev/spidev0.0 # SPI on?
|
||||||
|
sudo ./lora_rx -v --433 # step labels show exactly which command hangs
|
||||||
|
```
|
||||||
|
|
||||||
|
If it hangs at `Calibrate` — that's a TCXO config issue. Try in order:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ./lora_rx -v --433 --tcxo-none # skip TCXO entirely (crystal mode)
|
||||||
|
sudo ./lora_rx -v --433 --tcxo-27 # TCXO 2.7V
|
||||||
|
sudo ./lora_rx -v --433 --tcxo-33 # TCXO 3.3V (default)
|
||||||
|
```
|
||||||
|
|
||||||
|
The one that gets past "Calibrate done" is your module's config.
|
||||||
|
Use the same TCXO flag on TX and RX.
|
||||||
|
|
||||||
|
If using the 2.4 GHz antenna instead:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ./lora_rx -v --24
|
||||||
|
sudo ./lora_tx -v --24
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IMU debug
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo i2cdetect -y 1 # 0x68 or 0x69 must show
|
||||||
|
sudo ./imu_test -v # auto-detects both addresses
|
||||||
|
sudo ./imu_test -v -r # raw 16-bit values
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing on i2cdetect: I2C not enabled, wrong wiring, or no pull-ups on SDA/SCL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GPS debug
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls -la /dev/serial0
|
||||||
|
stty -F /dev/serial0 38400 raw && cat /dev/serial0 # raw NMEA bytes
|
||||||
|
./gps_test -v -a # all sentences
|
||||||
|
./gps_test -b 9600 # try different baud
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TCXO voltage reference
|
||||||
|
|
||||||
|
| Flag | Value | Voltage |
|
||||||
|
|------|-------|---------|
|
||||||
|
| `--tcxo-none` | 0xFF | no TCXO (crystal) |
|
||||||
|
| `--tcxo-27` | 0x05 | 2.7V |
|
||||||
|
| `--tcxo-33` | 0x07 | 3.3V |
|
||||||
|
| `--tcxo-v N` | 0x00–0x07 | raw byte |
|
||||||
@ -1,19 +1,21 @@
|
|||||||
// gps.hpp — u-blox NMEA GPS reader over UART
|
// gps.hpp — u-blox NMEA GPS reader over UART
|
||||||
// No external libs; uses POSIX termios + read().
|
// No external libs; uses POSIX termios + read().
|
||||||
//
|
//
|
||||||
// Hardware pins (THE TRUTH):
|
// Hardware (THE TRUTH):
|
||||||
// GPIO14/TXD0 → UBLOX_TX line (/dev/serial0)
|
// GPIO14/TXD0 → UBLOX_TX (/dev/serial0)
|
||||||
// GPIO15/RXD0 → UBLOX_RX line
|
// GPIO15/RXD0 → UBLOX_RX
|
||||||
// Default baud: 9600
|
// Default baud: 38400 (factory default on this module)
|
||||||
//
|
//
|
||||||
// Enable UART: sudo raspi-config → Interfaces → Serial Port
|
// Enable UART: sudo raspi-config → Interfaces → Serial Port
|
||||||
// "login shell over serial" = No, "serial port hardware" = Yes
|
// "login shell over serial" = No, "serial port hardware" = Yes
|
||||||
|
// Then reboot. Check: ls -la /dev/serial0
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdio>
|
#include <chrono>
|
||||||
#include <cstring>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
@ -22,54 +24,93 @@
|
|||||||
namespace gps {
|
namespace gps {
|
||||||
|
|
||||||
struct Fix {
|
struct Fix {
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
double lat = 0.0; // degrees, positive=N
|
double lat = 0.0; // degrees, positive=N
|
||||||
double lon = 0.0; // degrees, positive=E
|
double lon = 0.0; // degrees, positive=E
|
||||||
float speed_knots = 0.0f;
|
float speed_knots = 0.0f;
|
||||||
char time[12]{}; // HHMMSS.ss (raw NMEA)
|
char time[12]{}; // HHMMSS.ss (raw NMEA)
|
||||||
char date[7]{}; // DDMMYY
|
char date[7]{}; // DDMMYY
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
const char *dev = "/dev/serial0";
|
||||||
|
int baud = 38400; // confirmed working baud for this module
|
||||||
|
bool verbose = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Reader {
|
class Reader {
|
||||||
public:
|
public:
|
||||||
bool begin(const char *dev = "/dev/serial0", int baud = 9600);
|
bool begin(const Config &cfg);
|
||||||
|
bool begin(const char *dev = "/dev/serial0", int baud = 38400);
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
// Read one NMEA sentence into buf (null-terminated). Returns false on error.
|
// Read one NMEA sentence (null-terminated, includes '\n').
|
||||||
bool readLine(char *buf, size_t cap);
|
// timeout_ms: 0 = block forever, >0 = return false after timeout.
|
||||||
|
bool readLine(char *buf, size_t cap, uint32_t timeout_ms = 0);
|
||||||
|
|
||||||
// Parse $GPRMC or $GNRMC from a sentence. Returns false if sentence is
|
// Parse $GPRMC or $GNRMC. Returns false if wrong sentence type or fix void.
|
||||||
// not RMC or position is invalid.
|
|
||||||
static bool parseRmc(const char *sentence, Fix &fix);
|
static bool parseRmc(const char *sentence, Fix &fix);
|
||||||
|
|
||||||
|
// Verify NMEA checksum: XOR of bytes between '$' and '*'.
|
||||||
|
static bool verifyChecksum(const char *sentence);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int fd_ = -1;
|
int fd_ = -1;
|
||||||
|
bool verbose_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- Implementation ---------------------------------------------------------
|
||||||
|
|
||||||
|
inline bool Reader::begin(const Config &c)
|
||||||
|
{
|
||||||
|
verbose_ = c.verbose;
|
||||||
|
return begin(c.dev, c.baud);
|
||||||
|
}
|
||||||
|
|
||||||
inline bool Reader::begin(const char *dev, int baud)
|
inline bool Reader::begin(const char *dev, int baud)
|
||||||
{
|
{
|
||||||
fd_ = ::open(dev, O_RDWR | O_NOCTTY | O_NDELAY);
|
fd_ = ::open(dev, O_RDWR | O_NOCTTY | O_NDELAY);
|
||||||
if (fd_ < 0) return false;
|
if (fd_ < 0) {
|
||||||
::fcntl(fd_, F_SETFL, 0); // blocking
|
if (verbose_)
|
||||||
|
std::fprintf(stderr, "[gps] cannot open %s: %m\n", dev);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
::fcntl(fd_, F_SETFL, 0); // blocking reads
|
||||||
|
|
||||||
termios t{};
|
termios t{};
|
||||||
if (tcgetattr(fd_, &t) < 0) return false;
|
if (tcgetattr(fd_, &t) < 0) {
|
||||||
|
if (verbose_) std::fprintf(stderr, "[gps] tcgetattr failed: %m\n");
|
||||||
|
::close(fd_); fd_ = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
speed_t spd;
|
speed_t spd;
|
||||||
switch (baud) {
|
switch (baud) {
|
||||||
case 4800: spd = B4800; break;
|
case 4800: spd = B4800; break;
|
||||||
case 9600: spd = B9600; break;
|
case 9600: spd = B9600; break;
|
||||||
case 19200: spd = B19200; break;
|
case 19200: spd = B19200; break;
|
||||||
case 38400: spd = B38400; break;
|
case 38400: spd = B38400; break;
|
||||||
case 57600: spd = B57600; break;
|
case 57600: spd = B57600; break;
|
||||||
default: spd = B9600;
|
case 115200: spd = B115200; break;
|
||||||
|
default:
|
||||||
|
if (verbose_)
|
||||||
|
std::fprintf(stderr, "[gps] unknown baud %d, defaulting to 38400\n", baud);
|
||||||
|
spd = B38400;
|
||||||
}
|
}
|
||||||
cfsetispeed(&t, spd);
|
cfsetispeed(&t, spd);
|
||||||
cfsetospeed(&t, spd);
|
cfsetospeed(&t, spd);
|
||||||
cfmakeraw(&t);
|
cfmakeraw(&t);
|
||||||
t.c_cc[VMIN] = 1;
|
t.c_cc[VMIN] = 1;
|
||||||
t.c_cc[VTIME] = 0;
|
t.c_cc[VTIME] = 0;
|
||||||
tcsetattr(fd_, TCSANOW, &t);
|
if (tcsetattr(fd_, TCSANOW, &t) < 0) {
|
||||||
|
if (verbose_) std::fprintf(stderr, "[gps] tcsetattr failed: %m\n");
|
||||||
|
::close(fd_); fd_ = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tcflush(fd_, TCIOFLUSH);
|
||||||
|
|
||||||
|
if (verbose_)
|
||||||
|
std::fprintf(stderr, "[gps] opened %s at %d baud\n", dev, baud);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,18 +119,37 @@ inline void Reader::end()
|
|||||||
if (fd_ >= 0) { ::close(fd_); fd_ = -1; }
|
if (fd_ >= 0) { ::close(fd_); fd_ = -1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Reader::readLine(char *buf, size_t cap)
|
inline bool Reader::readLine(char *buf, size_t cap, uint32_t timeout_ms)
|
||||||
{
|
{
|
||||||
|
using clock = std::chrono::steady_clock;
|
||||||
|
auto deadline = clock::now() + std::chrono::milliseconds(timeout_ms);
|
||||||
|
|
||||||
|
auto timed_read = [&](char &c) -> bool {
|
||||||
|
if (timeout_ms == 0) {
|
||||||
|
return ::read(fd_, &c, 1) == 1;
|
||||||
|
}
|
||||||
|
// Non-blocking check with timeout.
|
||||||
|
while (true) {
|
||||||
|
ssize_t r = ::read(fd_, &c, 1);
|
||||||
|
if (r == 1) return true;
|
||||||
|
if (clock::now() >= deadline) return false;
|
||||||
|
// Brief yield to avoid spinning.
|
||||||
|
struct timespec ts = { 0, 1'000'000 }; // 1 ms
|
||||||
|
nanosleep(&ts, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
char c;
|
char c;
|
||||||
// Wait for '$' (start of NMEA sentence)
|
|
||||||
|
// Wait for '$' (start of sentence).
|
||||||
do {
|
do {
|
||||||
if (::read(fd_, &c, 1) != 1) return false;
|
if (!timed_read(c)) return false;
|
||||||
} while (c != '$');
|
} while (c != '$');
|
||||||
buf[n++] = '$';
|
buf[n++] = '$';
|
||||||
|
|
||||||
while (n < cap - 1) {
|
while (n < cap - 1) {
|
||||||
if (::read(fd_, &c, 1) != 1) return false;
|
if (!timed_read(c)) return false;
|
||||||
buf[n++] = c;
|
buf[n++] = c;
|
||||||
if (c == '\n') break;
|
if (c == '\n') break;
|
||||||
}
|
}
|
||||||
@ -97,23 +157,38 @@ inline bool Reader::readLine(char *buf, size_t cap)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse $GPRMC or $GNRMC
|
inline bool Reader::verifyChecksum(const char *sentence)
|
||||||
// Format: $GNRMC,HHMMSS.ss,A,DDMM.MMMM,N,DDDMM.MMMM,E,spd,crs,DDMMYY,...
|
{
|
||||||
|
// Checksum is XOR of all bytes between '$' (exclusive) and '*' (exclusive).
|
||||||
|
const char *p = sentence;
|
||||||
|
if (*p == '$') ++p;
|
||||||
|
|
||||||
|
uint8_t calc = 0;
|
||||||
|
while (*p && *p != '*') calc ^= (uint8_t)*p++;
|
||||||
|
if (*p != '*') return false; // no checksum present
|
||||||
|
|
||||||
|
unsigned int given = 0;
|
||||||
|
if (std::sscanf(p + 1, "%02X", &given) != 1) return false;
|
||||||
|
return (uint8_t)given == calc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse $GPRMC or $GNRMC.
|
||||||
|
// Format: $GNRMC,HHMMSS.ss,A,DDMM.MMMM,N,DDDMM.MMMM,E,spd,crs,DDMMYY,...*CS
|
||||||
inline bool Reader::parseRmc(const char *sentence, Fix &fix)
|
inline bool Reader::parseRmc(const char *sentence, Fix &fix)
|
||||||
{
|
{
|
||||||
if (!sentence) return false;
|
if (!sentence) return false;
|
||||||
|
|
||||||
// Check sentence type
|
|
||||||
if (std::strncmp(sentence, "$GPRMC", 6) != 0 &&
|
if (std::strncmp(sentence, "$GPRMC", 6) != 0 &&
|
||||||
std::strncmp(sentence, "$GNRMC", 6) != 0)
|
std::strncmp(sentence, "$GNRMC", 6) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Make a mutable copy
|
// Make a mutable copy for tokenization (strip the *CS checksum tail).
|
||||||
char buf[128];
|
char buf[128];
|
||||||
std::strncpy(buf, sentence, sizeof(buf) - 1);
|
std::strncpy(buf, sentence, sizeof(buf) - 1);
|
||||||
buf[sizeof(buf) - 1] = '\0';
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
// Terminate at '*' so strtok doesn't swallow the checksum as a field.
|
||||||
|
char *star = std::strchr(buf, '*');
|
||||||
|
if (star) *star = '\0';
|
||||||
|
|
||||||
// Tokenize on commas
|
|
||||||
const char *fields[13]{};
|
const char *fields[13]{};
|
||||||
int nf = 0;
|
int nf = 0;
|
||||||
char *p = buf;
|
char *p = buf;
|
||||||
@ -126,31 +201,22 @@ inline bool Reader::parseRmc(const char *sentence, Fix &fix)
|
|||||||
}
|
}
|
||||||
if (nf < 10) return false;
|
if (nf < 10) return false;
|
||||||
|
|
||||||
// field[2] = status ('A'=valid, 'V'=void)
|
|
||||||
if (fields[2][0] != 'A') { fix.valid = false; return false; }
|
if (fields[2][0] != 'A') { fix.valid = false; return false; }
|
||||||
|
|
||||||
// field[1] = time
|
|
||||||
std::strncpy(fix.time, fields[1], sizeof(fix.time) - 1);
|
std::strncpy(fix.time, fields[1], sizeof(fix.time) - 1);
|
||||||
|
|
||||||
// field[3] = lat DDMM.MMMM, field[4] = N/S
|
|
||||||
double raw_lat = std::atof(fields[3]);
|
double raw_lat = std::atof(fields[3]);
|
||||||
int lat_deg = (int)(raw_lat / 100.0);
|
int lat_deg = (int)(raw_lat / 100.0);
|
||||||
double lat_min = raw_lat - lat_deg * 100.0;
|
fix.lat = lat_deg + (raw_lat - lat_deg * 100.0) / 60.0;
|
||||||
fix.lat = lat_deg + lat_min / 60.0;
|
|
||||||
if (fields[4][0] == 'S') fix.lat = -fix.lat;
|
if (fields[4][0] == 'S') fix.lat = -fix.lat;
|
||||||
|
|
||||||
// field[5] = lon DDDMM.MMMM, field[6] = E/W
|
|
||||||
double raw_lon = std::atof(fields[5]);
|
double raw_lon = std::atof(fields[5]);
|
||||||
int lon_deg = (int)(raw_lon / 100.0);
|
int lon_deg = (int)(raw_lon / 100.0);
|
||||||
double lon_min = raw_lon - lon_deg * 100.0;
|
fix.lon = lon_deg + (raw_lon - lon_deg * 100.0) / 60.0;
|
||||||
fix.lon = lon_deg + lon_min / 60.0;
|
|
||||||
if (fields[6][0] == 'W') fix.lon = -fix.lon;
|
if (fields[6][0] == 'W') fix.lon = -fix.lon;
|
||||||
|
|
||||||
fix.speed_knots = (float)std::atof(fields[7]);
|
fix.speed_knots = (float)std::atof(fields[7]);
|
||||||
|
|
||||||
// field[9] = date DDMMYY
|
|
||||||
std::strncpy(fix.date, fields[9], sizeof(fix.date) - 1);
|
std::strncpy(fix.date, fields[9], sizeof(fix.date) - 1);
|
||||||
|
|
||||||
fix.valid = true;
|
fix.valid = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,82 @@
|
|||||||
|
// gps_test.cpp — u-blox GPS NMEA test
|
||||||
|
// Usage: ./gps_test [-v] [-a] [-b BAUD]
|
||||||
|
// -v verbose init debug
|
||||||
|
// -a print ALL NMEA sentences (not just RMC)
|
||||||
|
// -b BAUD set baud rate (default 38400)
|
||||||
|
//
|
||||||
|
// Enable UART: sudo raspi-config → Interfaces → Serial Port
|
||||||
|
// login shell: No, serial hardware: Yes → reboot
|
||||||
|
// Check: ls -la /dev/serial0
|
||||||
|
// Sniff raw bytes to confirm module is transmitting:
|
||||||
|
// stty -F /dev/serial0 38400 raw && cat /dev/serial0
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include "gps.hpp"
|
#include "gps.hpp"
|
||||||
|
|
||||||
int main()
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
gps::Reader reader;
|
gps::Config cfg;
|
||||||
|
cfg.verbose = false;
|
||||||
|
bool show_all = false;
|
||||||
|
|
||||||
if (!reader.begin()) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
std::fprintf(stderr, "gps open failed\n"
|
if (std::strcmp(argv[i], "-v") == 0) {
|
||||||
" check /dev/serial0 (UART)\n"
|
cfg.verbose = true;
|
||||||
" enable serial: sudo raspi-config -> Interfaces -> Serial Port\n"
|
} else if (std::strcmp(argv[i], "-a") == 0) {
|
||||||
" login shell = No, hardware = Yes\n");
|
show_all = true;
|
||||||
|
} else if (std::strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
|
||||||
|
cfg.baud = std::atoi(argv[++i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("gps_test: %s @%d baud%s%s\n",
|
||||||
|
cfg.dev, cfg.baud,
|
||||||
|
cfg.verbose ? " [verbose]" : "",
|
||||||
|
show_all ? " [all sentences]" : "");
|
||||||
|
|
||||||
|
gps::Reader gps;
|
||||||
|
if (!gps.begin(cfg)) {
|
||||||
|
std::fprintf(stderr, "ERROR: cannot open %s\n"
|
||||||
|
" Check: UART enabled? (sudo raspi-config)\n"
|
||||||
|
" Check: ls -la /dev/serial0\n"
|
||||||
|
" Check: wiring TX=GPIO14 RX=GPIO15\n", cfg.dev);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
std::fprintf(stderr, "gps reading /dev/serial0 at 9600...\n");
|
std::printf("UART open — waiting for NMEA (Ctrl+C to stop)\n\n");
|
||||||
|
|
||||||
char line[128];
|
char line[128];
|
||||||
gps::Fix fix;
|
int fixes = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!reader.readLine(line, sizeof(line))) {
|
// 2s per sentence timeout — if nothing arrives, likely wiring or baud mismatch
|
||||||
std::fprintf(stderr, "uart read error\n");
|
if (!gps.readLine(line, sizeof(line), 2000)) {
|
||||||
break;
|
std::printf(" [timeout: no NMEA for 2s — check wiring and baud rate]\n");
|
||||||
}
|
|
||||||
if (!gps::Reader::parseRmc(line, fix)) {
|
|
||||||
// Not an RMC sentence or no fix — print raw line for debug
|
|
||||||
// std::fprintf(stderr, "raw: %s", line);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (fix.valid) {
|
|
||||||
std::fprintf(stderr, "fix lat=%+.6f lon=%+.6f spd=%.1f kn "
|
// Checksum validation
|
||||||
"time=%s date=%s\n",
|
bool cs_ok = gps::Reader::verifyChecksum(line);
|
||||||
fix.lat, fix.lon, fix.speed_knots,
|
if (cfg.verbose && !cs_ok)
|
||||||
fix.time, fix.date);
|
std::printf(" [bad checksum] %s", line);
|
||||||
} else {
|
|
||||||
std::fprintf(stderr, "no fix\n");
|
if (show_all) {
|
||||||
|
std::printf("%s", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
gps::Fix fix;
|
||||||
|
if (gps::Reader::parseRmc(line, fix)) {
|
||||||
|
if (fix.valid) {
|
||||||
|
std::printf("[%4d] %s %s | lat=%+.6f lon=%+.6f spd=%.1fkn%s\n",
|
||||||
|
++fixes, fix.date, fix.time,
|
||||||
|
fix.lat, fix.lon, fix.speed_knots,
|
||||||
|
cs_ok ? "" : " [bad cs]");
|
||||||
|
} else {
|
||||||
|
std::printf(" no fix (GPRMC/GNRMC status=V)\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gps.end();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
38
math_example/README.md
Normal file
38
math_example/README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# math_example
|
||||||
|
|
||||||
|
Basic pixel → 3D vertex projection. Each pixel's brightness is used as depth in a
|
||||||
|
pinhole back-projection:
|
||||||
|
|
||||||
|
```
|
||||||
|
x = (u - cx) / fx * depth
|
||||||
|
y = (v - cy) / fy * depth
|
||||||
|
z = depth
|
||||||
|
```
|
||||||
|
|
||||||
|
Output is a triangulated OBJ mesh. Bright pixels project far from the camera,
|
||||||
|
dark pixels stay close. Gives a height-map-like 3D surface from any image.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Needs only the C++ stdlib and `stb_image` from `../shader_example/`.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./math # 128x128 sine-wave test pattern
|
||||||
|
./math ../shader_example/frame1.jpg # load an image
|
||||||
|
./math ../shader_example/frame1.jpg 2 # step=2 (denser mesh)
|
||||||
|
./math ../shader_example/frame1.jpg 4 90 # step=4, FOV 90°
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `out.obj` — open in Blender (`File → Import → Wavefront`) or MeshLab.
|
||||||
|
|
||||||
|
## What it's for
|
||||||
|
|
||||||
|
This is the math layer that sits between the camera and the LoRa transmitter.
|
||||||
|
Instead of sending raw pixels, you project them to vertices, run motion detection
|
||||||
|
in that 3D space, and transmit only the diff vertices over LoRa.
|
||||||
10
shader_example/README.md
Normal file
10
shader_example/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# shader_example
|
||||||
|
|
||||||
|
Loads `frame1.jpg` and `frame2.jpg`, runs `motion.frag` (per-pixel `|A - B|`)
|
||||||
|
on the GPU, writes result to `frameO.jpg`. Desktop GL 3.3, GLFW + GLEW.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
g++ shader.cpp -o shader -lglfw -lGLEW -lGL && ./shader
|
||||||
|
```
|
||||||
|
|
||||||
|
`stb_image.hpp` and `stb_image_write.hpp` are vendored in this folder.
|
||||||
Loading…
Reference in New Issue
Block a user