microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
spi.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
7#include "../microfmt.hpp"
8#include <cstddef>
9#include <cstdint>
10#include <string_view>
11
21namespace microfmt {
22
23// ============================================================================
24// SPI Modes & Flags
25// ============================================================================
26
27enum class spi_mode : uint8_t {
28 mode0 = 0, // CPOL=0, CPHA=0
29 mode1 = 1, // CPOL=0, CPHA=1
30 mode2 = 2, // CPOL=1, CPHA=0
31 mode3 = 3 // CPOL=1, CPHA=1
32};
33
34enum class spi_status : uint8_t { ok = 0, timeout, crc_err, overrun };
35
36// ============================================================================
37// SPI Transfer View Descriptor
38// ============================================================================
39
42 span<const uint8_t> mosi{}; // TX data (can be empty for half-duplex RX)
43 span<const uint8_t> miso{}; // RX data (can be empty for half-duplex TX)
44 uint8_t cs_pin{0}; // Chip select index (CS0, CS1, etc.)
45 spi_mode mode{spi_mode::mode0};
46 spi_status status{spi_status::ok};
47};
48
49// ============================================================================
50// Factory Helpers
51// ============================================================================
52
53// Full-duplex MOSI/MISO transfer
55[[nodiscard]] constexpr spi_transfer_view spi_duplex(span<const uint8_t> mosi, span<const uint8_t> miso, uint8_t cs = 0,
57 spi_status status = spi_status::ok) noexcept {
58 return {mosi, miso, cs, mode, status};
59}
60
61template <size_t N1, size_t N2>
62[[nodiscard]] constexpr spi_transfer_view spi_duplex(const uint8_t (&mosi)[N1], const uint8_t (&miso)[N2],
63 uint8_t cs = 0, spi_mode mode = spi_mode::mode0,
64 spi_status status = spi_status::ok) noexcept {
65 return {span<const uint8_t>(mosi, N1), span<const uint8_t>(miso, N2), cs, mode, status};
66}
67
68// Simplex/Half-duplex MOSI write
70[[nodiscard]] constexpr spi_transfer_view spi_write(span<const uint8_t> mosi, uint8_t cs = 0,
72 spi_status status = spi_status::ok) noexcept {
73 return {mosi, span<const uint8_t>{}, cs, mode, status};
74}
75
76template <size_t N>
77[[nodiscard]] constexpr spi_transfer_view spi_write(const uint8_t (&mosi)[N], uint8_t cs = 0,
79 spi_status status = spi_status::ok) noexcept {
80 return {span<const uint8_t>(mosi, N), span<const uint8_t>{}, cs, mode, status};
81}
82
83// Simplex/Half-duplex MISO read
85[[nodiscard]] constexpr spi_transfer_view spi_read(span<const uint8_t> miso, uint8_t cs = 0,
87 spi_status status = spi_status::ok) noexcept {
88 return {span<const uint8_t>{}, miso, cs, mode, status};
89}
90
91template <size_t N>
92[[nodiscard]] constexpr spi_transfer_view spi_read(const uint8_t (&miso)[N], uint8_t cs = 0,
94 spi_status status = spi_status::ok) noexcept {
95 return {span<const uint8_t>{}, span<const uint8_t>(miso, N), cs, mode, status};
96}
97
98// ============================================================================
99// Formatter Specialization for spi_transfer_view
100// ============================================================================
101
102template <> struct formatter<spi_transfer_view> {
103 bool compact{false};
104 bool uppercase_hex{true};
105
106 constexpr void parse(format_parse_context &ctx) noexcept {
107 for (char c : ctx.spec()) {
108 if (c == 'c' || c == 'C')
109 compact = true; // {:c} -> CS0:TX[...]/RX[...]
110 if (c == 'x')
111 uppercase_hex = false;
112 }
113 }
114
115 [[nodiscard]] static constexpr microfmt::string_view status_name(spi_status status) noexcept {
116 switch (status) {
117 case spi_status::ok:
118 return "OK";
120 return "TIMEOUT";
122 return "CRC_ERR";
124 return "OVERRUN";
125 }
126 return "UNKNOWN";
127 }
128
129 void format(const spi_transfer_view &tx, const sink &out) const noexcept {
130 const auto &hex = uppercase_hex ? detail::hex_digits_upper : detail::hex_digits_lower;
131
132 const auto print_bytes = [&out, &hex](span<const uint8_t> s) noexcept {
133 for (size_t i = 0; i < s.size(); ++i) {
134 if (i > 0)
135 out.put(' ');
136 const uint8_t b = s[i];
137 out.put(hex[(b >> 4) & 0x0F]);
138 out.put(hex[b & 0x0F]);
139 }
140 };
141
142 if (compact) {
143 // Compact: CS0:TX[9F 00 00]/RX[00 EF 40]
144 out.write("CS");
145 detail::format_unsigned<detail::radix::decimal>(out, tx.cs_pin, false, 0);
146 out.put(':');
147
148 if (!tx.mosi.empty()) {
149 out.write("TX[");
150 print_bytes(tx.mosi);
151 out.put(']');
152 }
153
154 if (!tx.mosi.empty() && !tx.miso.empty()) {
155 out.put('/');
156 }
157
158 if (!tx.miso.empty()) {
159 out.write("RX[");
160 print_bytes(tx.miso);
161 out.put(']');
162 }
163
164 if (tx.status != spi_status::ok) {
165 out.write("!(");
166 out.write(status_name(tx.status));
167 out.put(')');
168 }
169 return;
170 }
171
172 // Verbose human-readable transfer inspect:
173 // SPI [CS0, Mode 0] (3 B) MOSI: 9F 00 00 | MISO: 00 EF 40 -> OK
174 out.write("SPI [CS");
175 detail::format_unsigned<detail::radix::decimal>(out, tx.cs_pin, false, 0);
176 out.write(", Mode ");
177 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint8_t>(tx.mode), false, 0);
178 out.write("] (");
179
180 const size_t total_len = (tx.mosi.size() >= tx.miso.size()) ? tx.mosi.size() : tx.miso.size();
181 detail::format_unsigned<detail::radix::decimal>(out, total_len, false, 0);
182 out.write(" B)");
183
184 if (!tx.mosi.empty()) {
185 out.write(" MOSI: ");
186 print_bytes(tx.mosi);
187 }
188
189 if (!tx.miso.empty()) {
190 if (!tx.mosi.empty())
191 out.write(" |");
192 out.write(" MISO: ");
193 print_bytes(tx.miso);
194 }
195
196 out.write(" -> ");
197 out.write(status_name(tx.status));
198 }
199};
200
201} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
spi_mode
Definition spi.hpp:27
constexpr hex_view< T > hex(T val, int width=0, bool prefix=false, bool uppercase=false) noexcept
Definition format_helpers.hpp:29
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
constexpr spi_transfer_view spi_write(span< const uint8_t > mosi, uint8_t cs=0, spi_mode mode=spi_mode::mode0, spi_status status=spi_status::ok) noexcept
Creates a write-only SPI transfer view.
Definition spi.hpp:70
constexpr spi_transfer_view spi_read(span< const uint8_t > miso, uint8_t cs=0, spi_mode mode=spi_mode::mode0, spi_status status=spi_status::ok) noexcept
Creates a read-only SPI transfer view.
Definition spi.hpp:85
spi_status
Definition spi.hpp:34
constexpr spi_transfer_view spi_duplex(span< const uint8_t > mosi, span< const uint8_t > miso, uint8_t cs=0, spi_mode mode=spi_mode::mode0, spi_status status=spi_status::ok) noexcept
Creates a full-duplex SPI transfer view.
Definition spi.hpp:55
constexpr void parse(format_parse_context &ctx) noexcept
Definition spi.hpp:106
void format(const spi_transfer_view &tx, const sink &out) const noexcept
Definition spi.hpp:129
static constexpr microfmt::string_view status_name(spi_status status) noexcept
Definition spi.hpp:115
Definition microfmt.hpp:985
Non-owning simplex or full-duplex SPI transfer descriptor.
Definition spi.hpp:41