You wouldn't need to delete it; instead that is the first place you overwrite. If you start with a fixed-size buffer, you also want to store an index to that NUL, which is effectively also its current length. This way, you don't have to continuously re-scan to find the NUL. Put that in a struct to keep those together
struct LogCharBuffer {
char _buf[1000];
size_t _len;
LogCharBuffer() {
clear();
}
void clear() {
_len = 0;
terminate();
}
void terminate() {
_buf[_len] = '\0';
}
};
Inside this named struct, if you define a function with the same name, it gets executed whenever you create an instance of the struct. It also has no separately declared return type; this is a constructor. You could have that call other functions that are also members of the struct, which are called methods. So in this case, constructor ==> clear
==> terminate
.
Then you add a method to receive any data you get
void log(const char *msg) {
log(msg, strlen(msg));
}
void log(const char *msg, size_t len) {
while (len) {
if (_len + len >= sizeof(_buf)) {
flush();
}
while (len && _len < sizeof(_buf) - 1) {
_buf[_len++] = *msg++;
--len;
}
terminate();
}
}
Actually two methods with the same name, but with different type signatures. This is function overloading. If you pass just a char *
(const
or not), use strlen
to figure out how long the C-string is. But if you already know, you can pass that directly. Then
- the outer loop repeats while there is
len
remaining. It could be initially zero, in which case nothing happens at all, or until the entire incoming message is handled
- If the current length plus the incoming length will fill the buffer entirely or overflow, then there is no room for the new terminating NUL. In that case we'll
flush
it first. More on that later
- the inner loop is basic C pointer stuff that intimidates some
- while there is some incoming left to do
- and there is space remaining: buffer size minus one to leave room for the terminating NUL
- copy one byte over, updating the buffer-length and incoming pointer
- and reduce the incoming length accordingly
- add a new NUL after the copy so far
In the flush
method, you do whatever you really want to do with the data, and then clear
it. That can in theory be called directly if you know you're shutting down, or periodically, so you don't lose stuff.
void flush() {
commit();
clear();
}
void commit() {
//TODO replace with what you really want to do
for (int i = 0; i < _len; ++i) {
if (i % 72 == 0) {
Serial.print("\n--> ");
}
Serial.print(_buf[i]);
}
Serial.println();
}
That whatever is in a method called commit
, and here is a placeholder: print the buffer with a hard wrap at 72 characters. You would save to wherever. Note that nothing so far has actually depended on the fact that the _buf
is NUL-terminated. But you could here; for example, just Serial.println(_buf)
to print the whole thing.
With this struct in hand, you can try it in the Serial Monitor with this sketch, although you will probably want to reduce the _buf
size from 1000
to 100
, and the hard-wrap from 72
to 30
to make the effects more immediate
LogCharBuffer logBuffer;
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("OK\nStart typing");
}
void loop() {
char buf[15];
if (int len = Serial.readBytes(buf, sizeof(buf)); len > 0) {
if (len == 1 && buf[0] == '\n') {
Serial.println("flush");
logBuffer.flush();
} else {
Serial.print("read "); Serial.println(len);
logBuffer.log(buf, len);
}
}
delay(1);
}
The loop
uses a randomly-sized buffer of its own to read the serial input. It could also be bigger than the one in LogCharBuffer
. Pressing Enter by itself in the Serial Monitor will print anything remaining.