Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
sd_output.hpp
Go to the documentation of this file.
1/*
2 * sd_output.hpp — SD card log sink for SBL logger
3 *
4 * Per-boot sequential log files with size and count limits.
5 * Buffers writes to a 512-byte sector-aligned buffer and flushes
6 * to SD card via FatFs. Call flush() periodically from the main
7 * loop (never from ISR).
8 *
9 * Usage:
10 * // Mount FatFs first, then:
11 * SdLogConfig cfg;
12 * cfg.millis = sbl::driver::Timer::millis; // required
13 * SdLogOutput::init(cfg);
14 *
15 * // In main loop:
16 * SdLogOutput::write("hello\r\n");
17 * SdLogOutput::flush(); // time-gated, safe to call every iteration
18 */
19
20#ifndef SBL_COMMON_LOG_SD_OUTPUT_HPP_
21#define SBL_COMMON_LOG_SD_OUTPUT_HPP_
22
23#include "ff.h"
24#include <cstring>
25
26namespace sbl::log {
27
29 const char* prefix = "LOG"; // Filename prefix → LOG_000.TXT
30 size_t max_file_bytes = 4*1024*1024;// 4MB per file (~24h of debug output)
31 uint16_t max_files = 256; // 256 files × 4MB = 1GB max card usage
32 uint16_t flush_interval_ms = 100; // Min ms between flushes (0 = every call)
33 uint32_t (*millis)() = nullptr; // Millisecond timestamp function (required)
34};
35
37public:
38 /// Initialize: scan existing files, create next sequential log.
39 /// Returns true if log file was opened successfully.
40 static bool init(const SdLogConfig& cfg = {}) {
41 s_cfg = cfg;
42 s_pos = 0;
43 s_file_bytes = 0;
44 s_last_flush_ms = 0;
45 s_open = false;
46
47 // Find the next available file number
48 s_current_num = find_next_file_number();
49
50 // If we've hit max_files, delete the oldest to make room
51 if (s_current_num >= s_cfg.max_files) {
52 delete_oldest();
53 s_current_num = find_next_file_number();
54 }
55
56 return open_file(s_current_num);
57 }
58
59 /// Logger sink interface — buffers data, auto-flushes when buffer is full.
60 static void write(const char* str) {
61 if (!s_open) return;
62 size_t len = strlen(str);
63 const char* p = str;
64 while (len > 0) {
65 size_t space = BUF_SIZE - s_pos;
66 size_t chunk = len < space ? len : space;
67 memcpy(s_buf + s_pos, p, chunk);
68 s_pos += chunk;
69 p += chunk;
70 len -= chunk;
71 if (s_pos >= BUF_SIZE) {
72 flush_buffer();
73 }
74 }
75 }
76
77 /// Time-gated flush. Safe to call every main loop iteration.
78 /// Only writes to SD if flush_interval_ms has elapsed or buffer is >75% full.
79 static void flush() {
80 if (!s_open || s_pos == 0) return;
81
82 bool time_to_flush = true;
83 if (s_cfg.millis && s_cfg.flush_interval_ms > 0) {
84 uint32_t now = s_cfg.millis();
85 uint32_t elapsed = now - s_last_flush_ms;
86 time_to_flush = (elapsed >= s_cfg.flush_interval_ms) ||
87 (s_pos >= BUF_SIZE * 3 / 4);
88 }
89
90 if (time_to_flush) {
91 flush_buffer();
92 }
93 }
94
95 /// Flush immediately, bypassing time gate.
96 static void force_flush() {
97 if (!s_open || s_pos == 0) return;
98 flush_buffer();
99 }
100
101 /// Close file (flushes first). Call before removing card.
102 static void close() {
103 if (!s_open) return;
104 force_flush();
105 f_close(&s_fil);
106 s_open = false;
107 }
108
109 static bool is_open() { return s_open; }
110
111private:
112 static constexpr size_t BUF_SIZE = 512;
113 static constexpr size_t NAME_BUF = 16; // "LOG_000.TXT\0"
114
115 static void make_filename(char* buf, uint16_t num) {
116 // Build PREFIX_NNN.TXT (8.3 format)
117 size_t i = 0;
118 const char* p = s_cfg.prefix;
119 while (*p && i < 4) buf[i++] = *p++;
120 buf[i++] = '_';
121 buf[i++] = '0' + (num / 100) % 10;
122 buf[i++] = '0' + (num / 10) % 10;
123 buf[i++] = '0' + num % 10;
124 buf[i++] = '.';
125 buf[i++] = 'T';
126 buf[i++] = 'X';
127 buf[i++] = 'T';
128 buf[i] = '\0';
129 }
130
131 static uint16_t find_next_file_number() {
132 // Find the lowest unused file number
133 char name[NAME_BUF];
134 FILINFO fno;
135 for (uint16_t n = 0; n < s_cfg.max_files; n++) {
136 make_filename(name, n);
137 if (f_stat(name, &fno) != FR_OK) {
138 return n; // File doesn't exist — use this number
139 }
140 }
141 return s_cfg.max_files; // All slots full
142 }
143
144 static void delete_oldest() {
145 // Delete file 0 and shift all others down
146 // Simple approach: just delete the lowest-numbered file
147 char name[NAME_BUF];
148 FILINFO fno;
149 for (uint16_t n = 0; n < s_cfg.max_files; n++) {
150 make_filename(name, n);
151 if (f_stat(name, &fno) == FR_OK) {
152 f_unlink(name);
153 return; // Deleted oldest, now find_next will find the gap
154 }
155 }
156 }
157
158 static bool open_file(uint16_t num) {
159 char name[NAME_BUF];
160 make_filename(name, num);
161 FRESULT fr = f_open(&s_fil, name, FA_WRITE | FA_CREATE_NEW);
162 if (fr != FR_OK) return false;
163 s_open = true;
164 s_file_bytes = 0;
165 return true;
166 }
167
168 static bool rotate_file() {
169 // Close current, open next
170 f_close(&s_fil);
171 s_open = false;
172 s_current_num++;
173
174 if (s_current_num >= s_cfg.max_files) {
175 delete_oldest();
176 s_current_num = find_next_file_number();
177 }
178
179 return open_file(s_current_num);
180 }
181
182 static void flush_buffer() {
183 if (s_pos == 0) return;
184
185 // Check if we need to rotate before writing
186 if (s_cfg.max_file_bytes > 0 &&
187 s_file_bytes + s_pos > s_cfg.max_file_bytes) {
188 if (!rotate_file()) {
189 s_pos = 0;
190 return; // Rotation failed, drop data
191 }
192 }
193
194 UINT bw;
195 f_write(&s_fil, s_buf, s_pos, &bw);
196 f_sync(&s_fil);
197 s_file_bytes += bw;
198 s_pos = 0;
199
200 if (s_cfg.millis) {
201 s_last_flush_ms = s_cfg.millis();
202 }
203 }
204
205 static inline SdLogConfig s_cfg;
206 static inline FIL s_fil;
207 static inline char s_buf[BUF_SIZE];
208 static inline size_t s_pos = 0;
209 static inline size_t s_file_bytes = 0;
210 static inline uint16_t s_current_num = 0;
211 static inline uint32_t s_last_flush_ms = 0;
212 static inline bool s_open = false;
213};
214
215} // namespace sbl::log
216
217#endif // SBL_COMMON_LOG_SD_OUTPUT_HPP_
static void close()
Close file (flushes first). Call before removing card.
static void flush()
Definition sd_output.hpp:79
static void force_flush()
Flush immediately, bypassing time gate.
Definition sd_output.hpp:96
static bool init(const SdLogConfig &cfg={})
Definition sd_output.hpp:40
static bool is_open()
static void write(const char *str)
Logger sink interface — buffers data, auto-flushes when buffer is full.
Definition sd_output.hpp:60
Logging.
Definition banner.hpp:51
const char * prefix
Definition sd_output.hpp:29
uint32_t(* millis)()
Definition sd_output.hpp:33