Sound Byte Libs 1ee2ca6
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
embedded_printf.hpp
Go to the documentation of this file.
1#ifndef SBL_CORE_UTIL_EMBEDDED_PRINTF_HPP_
2#define SBL_CORE_UTIL_EMBEDDED_PRINTF_HPP_
3
4/**
5 * @file embedded_printf.hpp
6 * @brief Lightweight printf implementation for ARM Cortex-M systems
7 * @ingroup util
8 *
9 * Minimal printf supporting only essential format specifiers:
10 * - %s - strings
11 * - %c - characters
12 * - %d - signed integers
13 * - %u - unsigned integers
14 * - %x - hex (lowercase)
15 * - %X - hex (uppercase)
16 * - %% - literal %
17 *
18 * Features:
19 * - ~200-300 bytes instead of 962 bytes for full vfprintf
20 * - No floating point support (can be added for Cortex-M4F with FPU)
21 * - No width/precision/flags (keeps it simple)
22 * - Fixed buffer size (no dynamic allocation)
23 * - ISR-safe (no global state)
24 *
25 * Usage:
26 * This is used automatically by the logging system when SBL_USE_EMBEDDED_PRINTF
27 * is defined. Otherwise, it falls back to system printf.
28 */
29
30#include <cstdint>
31#include <cstdarg>
32#include <cstddef>
34
35namespace sbl {
36namespace core {
37namespace util {
38
39/**
40 * @brief Lightweight printf implementation for ARM Cortex-M systems
41 *
42 * Supports only essential format specifiers to minimize code size.
43 * Perfect for embedded logging where full printf is overkill.
44 */
46public:
47 /**
48 * @brief Format string with arguments into buffer
49 *
50 * @param buffer Output buffer
51 * @param size Buffer size
52 * @param format Format string
53 * @param args Variable arguments
54 * @return Number of characters written (excluding null terminator)
55 */
56 static int vsnprintf(char* buffer, std::size_t size, const char* format, va_list args) {
57 if (!buffer || size == 0) return 0;
58
59 char* buf = buffer;
60 char* const buf_end = buffer + size - 1; // Reserve space for null terminator
61 const char* fmt = format;
62
63 while (*fmt && buf < buf_end) {
64 if (*fmt != '%') {
65 *buf++ = *fmt++;
66 continue;
67 }
68
69 fmt++; // Skip '%'
70
71 if (*fmt == '%') {
72 // Literal %
73 *buf++ = '%';
74 fmt++;
75 continue;
76 }
77
78 // Parse width specifier (simplified - just skip digits and flags)
79 while (*fmt && (*fmt >= '0' && *fmt <= '9')) {
80 fmt++; // Skip width digits
81 }
82
83 // Parse format specifier
84 switch (*fmt) {
85 case 'c': {
86 // Character
87 int c = va_arg(args, int);
88 *buf++ = static_cast<char>(c);
89 break;
90 }
91
92 case 's': {
93 // String
94 const char* str = va_arg(args, const char*);
95 if (!str) str = "(null)";
96
97 while (*str && buf < buf_end) {
98 *buf++ = *str++;
99 }
100 break;
101 }
102
103 case 'd': {
104 // Signed integer
105 int32_t value = va_arg(args, int32_t);
106 buf += formatInteger(buf, buf_end - buf, value, 10, false);
107 break;
108 }
109
110 case 'u': {
111 // Unsigned integer
112 uint32_t value = va_arg(args, uint32_t);
113 buf += formatUnsigned(buf, buf_end - buf, value, 10, false);
114 break;
115 }
116
117 case 'x': {
118 // Hex lowercase
119 uint32_t value = va_arg(args, uint32_t);
120 buf += formatUnsigned(buf, buf_end - buf, value, 16, false);
121 break;
122 }
123
124 case 'X': {
125 // Hex uppercase
126 uint32_t value = va_arg(args, uint32_t);
127 buf += formatUnsigned(buf, buf_end - buf, value, 16, true);
128 break;
129 }
130
131 default:
132 // Unknown format specifier - just output it
133 if (buf < buf_end) *buf++ = '%';
134 if (buf < buf_end) *buf++ = *fmt;
135 break;
136 }
137
138 fmt++;
139 }
140
141 *buf = '\0'; // Null terminate
142 return static_cast<int>(buf - buffer);
143 }
144
145 /**
146 * @brief Format string with arguments into buffer
147 *
148 * @param buffer Output buffer
149 * @param size Buffer size
150 * @param format Format string
151 * @param ... Variable arguments
152 * @return Number of characters written (excluding null terminator)
153 */
154 static int snprintf(char* buffer, std::size_t size, const char* format, ...) {
155 va_list args;
156 va_start(args, format);
157 int result = vsnprintf(buffer, size, format, args);
158 va_end(args);
159 return result;
160 }
161
162private:
163 /**
164 * @brief Format signed integer
165 *
166 * @param buffer Output buffer
167 * @param size Buffer size
168 * @param value Integer value
169 * @param base Number base (10, 16, etc.)
170 * @param uppercase Use uppercase letters for hex
171 * @return Number of characters written
172 */
173 static std::size_t formatInteger(char* buffer, std::size_t size, int32_t value, uint8_t base, bool uppercase) {
174 if (size == 0) return 0;
175
176 char* start = buffer;
177
178 // Handle negative numbers
179 if (value < 0) {
180 value = -value;
181 if (size > 0) {
182 *buffer++ = '-';
183 size--;
184 }
185 }
186
187 std::size_t len = formatUnsigned(buffer, size, static_cast<uint32_t>(value), base, uppercase);
188 return static_cast<std::size_t>(buffer - start) + len;
189 }
190
191 /**
192 * @brief Format unsigned integer
193 *
194 * @param buffer Output buffer
195 * @param size Buffer size
196 * @param value Unsigned integer value
197 * @param base Number base (10, 16, etc.)
198 * @param uppercase Use uppercase letters for hex
199 * @return Number of characters written
200 *
201 * @note Uses division/modulo for base conversion. This is acceptable for
202 * debug/logging paths but should not be used in time-critical code.
203 * For ARM Cortex-M0+ without hardware division, consider disabling
204 * printf support or using base-16 only (can use shifts).
205 */
206 static std::size_t formatUnsigned(char* buffer, std::size_t size, uint32_t value, uint8_t base, bool uppercase) {
207 if (size == 0) return 0;
208
209 char temp[sbl::defaults::SMALL_BUFFER]; // Enough for any 32-bit number in any base
210 char* temp_ptr = temp + sizeof(temp) - 1;
211 *temp_ptr = '\0';
212
213 if (value == 0) {
214 *--temp_ptr = '0';
215 } else {
216 const char* digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";
217
218 while (value > 0) {
219 *--temp_ptr = digits[value % base];
220 value /= base;
221 }
222 }
223
224 // Copy to output buffer
225 std::size_t len = 0;
226 while (*temp_ptr && len < size - 1) {
227 buffer[len++] = *temp_ptr++;
228 }
229
230 return len;
231 }
232};
233
234/**
235 * @brief Embedded printf functions
236 *
237 * These are the functions that logging will use when SBL_USE_EMBEDDED_PRINTF is defined.
238 */
239inline int embedded_vsnprintf(char* buffer, std::size_t size, const char* format, va_list args) {
240 return EmbeddedPrintf::vsnprintf(buffer, size, format, args);
241}
242
243inline int embedded_snprintf(char* buffer, std::size_t size, const char* format, ...) {
244 va_list args;
245 va_start(args, format);
246 int result = EmbeddedPrintf::vsnprintf(buffer, size, format, args);
247 va_end(args);
248 return result;
249}
250
251} // namespace util
252} // namespace core
253} // namespace sbl
254
255#endif // SBL_CORE_UTIL_EMBEDDED_PRINTF_HPP_
Lightweight printf implementation for ARM Cortex-M systems.
static int vsnprintf(char *buffer, std::size_t size, const char *format, va_list args)
Format string with arguments into buffer.
static int snprintf(char *buffer, std::size_t size, const char *format,...)
Format string with arguments into buffer.
Sensible default constants for embedded systems.
int embedded_vsnprintf(char *buffer, std::size_t size, const char *format, va_list args)
Embedded printf functions.
int embedded_snprintf(char *buffer, std::size_t size, const char *format,...)
constexpr std::size_t SMALL_BUFFER
Small buffer size for number formatting, etc.
Definition defaults.hpp:24
Root namespace for all Sound Byte Libs functionality.
Definition aliases.hpp:24