Events and SD Logging
The N6Events library's frame ring, motion gate and event lifecycle, the N6EventsSD sink, and SD card logging with SdFat.
The N6Events library keeps the most recent seconds of encoded frames in a PSRAM ring, runs the neural network only when a trigger indicates a change, and when the sketch declares an event, writes the frames before and after it to a sink and notifies the host. N6EventsSD is the SD card sink.
Model
The library knows four things: a ring of recent frames, an event, a clip and a sink. It knows nothing about sensors. A trigger is a boolean the sketch computes from any source: a motion score, an audio class, a thermal hotspot, a distance reading, a PIR input.
#include <OV5640_Arduino.h>
#include <PostProcess.h>
#include <SPI.h>
#include <SdFat.h>
#include <N6Events.h>
#include <N6EventsSD.h>
#pragma neuron6 model="yolov8n_256_quant_pc_uf_pose_coco-st.tflite" name=yolov8_mpe
OV5640 camera;
NEURON6_DECLARE_MODEL(yolov8_mpe);
static uint8_t snap[256 * 256 * 3] __attribute__((aligned(32), section(".ext_psram")));
static n6_motion_gate_t gate;
SdFat sd;
void setup() {
VisionConfig cfg;
cfg.camera = CAMERA_VGA; cfg.fps = FPS_15; cfg.nn.max_fps = 5;
Vision.begin(&NN_Instance_yolov8_mpe,
PostProcess_YOLOv8_MPE_OnSlot(0.4f, 0.5f, VND_META_SLOT_MODEL_0), cfg);
n6_events_cfg_t ec = N6_EVENTS_CFG_DEFAULT; // after Vision.begin(): it resets the pool
ec.pre_ms = 5000; ec.post_ms = 5000; ec.refractory_ms = 10000;
n6_events_begin(&ec);
n6_motion_gate_begin(&gate, 64, 48, NULL);
SPI.begin();
SPI.setKernelSource(RCC_SPI2CLKSOURCE_HSI);
if (sd.begin(SdSpiConfig(A5, DEDICATED_SPI, SD_SCK_MHZ(20), &SPI)))
n6_events_add_sink(n6_event_sink_sd(&sd, "/events"));
}
void loop() {
if (NN_capture_snapshot(snap, sizeof snap) > 0 &&
n6_motion_gate_update(&gate, snap, g_nn_out_w, g_nn_out_h)) { // the scene changed
Vision.run();
if (n6_events_last_boxes(60) > 0) // any model saw something
n6_events_fire(N6_EVT_SRC_MOTION | N6_EVT_SRC_NN, 1000);
}
n6_events_service(); // every loop
}Frame ring
The ring is fed by a synchronous observer, n6_frame_tap(), called from the single point every transport's frames pass through in the core, immediately before a frame is posted to the active queue. It receives the JPEG and the composed metadata for that frame, and sees USB and WiFi frames alike, including frames encoded with no host attached. Pre-roll comes from this tap.
The ring is a byte ring of variable-size records in caller-supplied memory. Records are never split across the end of the buffer. Iteration is valid only while the ring is paused.
Lifecycle
States are IDLE, POST_ROLL, WRITING, REFRACTORY and back to IDLE. A fire during post-roll extends the event. A fire during writing or refractory is deferred as a single pending event, so a burst produces at most two events.
The clip write runs synchronously inside n6_events_service() and blocks loop() for one to two seconds. Frames arriving during the write are dropped and counted in tap_dropped_paused; the camera and the host stream continue.
Triggers
n6_events_last_boxes(min_score) counts boxes above a score in the primary metadata slot. Every bundled post-processor emits boxes, so it is a model-independent trigger meaning "the network detected something". Other triggers are ordinary conditions:
if (n6_aed_top() == AED_DOG) n6_events_fire(N6_EVT_SRC_AUDIO, 800);n6_motion_gate_t compares successive downscaled snapshots and reports whether the scene changed. It is a packaged version of the difference-and-statistic test in Colour Blobs and Motion.
Outputs
A clip goes to the sink: one directory per event, a JPEG per frame, each frame's own metadata in meta.bin, and an event.txt. A sequence counter in /events/.seq survives power cycles.
The event, a 17-byte META_EVENT record, goes to the host. It is stamped on the video and re-announced without video for a configurable period, so a board with no video stream still delivers it. Neuro Studio shows events in an Events panel.
Clips do not go to USB. A connected host already has the live video, and producing a clip on the possibility that a host is watching is what the host contract forbids.
Ordering constraint
n6_events_begin() and n6_motion_gate_begin() allocate from the PSRAM pool, which Vision.begin() resets. They are called after it; otherwise the camera buffers overwrite the ring.
Dormant tier
With the one-shot PowerSaveCam engine there is no pre-roll, because nothing was encoding before the wake. The same ring, events and sinks apply: the sketch pushes one JPEG per wake with n6_events_push(), fires on a sensor condition, services, and sleeps. The EventRecordingLowPower example shows this.
SD card logging without events
Plain logging uses SdFat directly. The two platform-specific SPI calls are required; see SPI. The SleepAndWake example writes one file per cycle with a header, a JPEG, a thermal frame and a telemetry record, each at a recorded offset.
Separate library
N6EventsSD is a separate library because it depends on SdFat, and N6Events carries no external dependency. LTE and WiFi sinks are not built; the sink interface is three calls. H.264 clips are not supported, as the hardware produces JPEG only.
Verification status
EventsRingSelfTest in extras/bench tests the ring without a camera or card. EventsParked, also in extras/bench, composes the pipeline with the stream-pause functions in a combination that has not been verified on hardware.