microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
i2c.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// I2C Transfer Flags & Status
25// ============================================================================
26
27enum class i2c_dir : uint8_t { write = 0, read = 1 };
28
29enum class i2c_status : uint8_t { ok = 0, nack_addr, nack_data, arb_lost, timeout };
30
31enum class RELOCO_FLAG_ENUM i2c_flags : uint8_t {
32 none = 0,
33 ten_bit = 1 << 0, // 10-bit slave address
34 nostart = 1 << 1, // Continuation transfer without START condition
35 stop = 1 << 2 // Issue STOP condition after this message
36};
37
38// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
39RELOCO_BEGIN_UNSAFE_CODE
40[[nodiscard]] constexpr i2c_flags operator|(i2c_flags a, i2c_flags b) noexcept {
41 return static_cast<i2c_flags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
42}
44
45[[nodiscard]] constexpr bool operator&(i2c_flags a, i2c_flags b) noexcept {
46 return (static_cast<uint8_t>(a) & static_cast<uint8_t>(b)) != 0;
47}
48
49// ============================================================================
50// I2C Message View Descriptor
51// ============================================================================
52
54struct MICROFMT_API_CLASS RELOCO_POINTER i2c_msg_view {
55 uint16_t addr{0}; // 7-bit (0x00..0x7F) or 10-bit (0x000..0x3FF)
56 i2c_dir direction{i2c_dir::write};
57 span<const uint8_t> payload{};
58 i2c_status status{i2c_status::ok};
59 i2c_flags flags{i2c_flags::none};
60};
61
62// ============================================================================
63// Factory Helpers
64// ============================================================================
65
66// Standard 7-bit I2C write transaction
68[[nodiscard]] constexpr i2c_msg_view i2c_write(uint8_t addr_7bit, span<const uint8_t> data,
69 i2c_status status = i2c_status::ok) noexcept {
70 return {static_cast<uint16_t>(addr_7bit & 0x7F), i2c_dir::write, data, status, i2c_flags::none};
71}
72
73template <size_t N>
74[[nodiscard]] constexpr i2c_msg_view i2c_write(uint8_t addr_7bit, const uint8_t (&arr)[N],
75 i2c_status status = i2c_status::ok) noexcept {
76 return {static_cast<uint16_t>(addr_7bit & 0x7F), i2c_dir::write, span<const uint8_t>(arr, N), status,
77 i2c_flags::none};
78}
79
80// Standard 7-bit I2C read transaction
82[[nodiscard]] constexpr i2c_msg_view i2c_read(uint8_t addr_7bit, span<const uint8_t> data,
83 i2c_status status = i2c_status::ok) noexcept {
84 return {static_cast<uint16_t>(addr_7bit & 0x7F), i2c_dir::read, data, status, i2c_flags::none};
85}
86
87template <size_t N>
88[[nodiscard]] constexpr i2c_msg_view i2c_read(uint8_t addr_7bit, const uint8_t (&arr)[N],
89 i2c_status status = i2c_status::ok) noexcept {
90 return {static_cast<uint16_t>(addr_7bit & 0x7F), i2c_dir::read, span<const uint8_t>(arr, N), status, i2c_flags::none};
91}
92
93// 10-bit Extended address message
95[[nodiscard]] constexpr i2c_msg_view i2c_10bit(uint16_t addr_10bit, i2c_dir dir, span<const uint8_t> data,
96 i2c_status status = i2c_status::ok) noexcept {
97 return {static_cast<uint16_t>(addr_10bit & 0x3FF), dir, data, status, i2c_flags::ten_bit};
98}
99
100// ============================================================================
101// Formatter Specialization for i2c_msg_view
102// ============================================================================
103
104template <> struct formatter<i2c_msg_view> {
105 bool compact{false};
106 bool uppercase_hex{true};
107
108 constexpr void parse(format_parse_context &ctx) noexcept {
109 for (char c : ctx.spec()) {
110 if (c == 'c' || c == 'C')
111 compact = true; // {:c} -> 0x68:W[0x75]
112 if (c == 'x')
113 uppercase_hex = false;
114 }
115 }
116
117 [[nodiscard]] static constexpr microfmt::string_view status_name(i2c_status status) noexcept {
118 switch (status) {
119 case i2c_status::ok:
120 return "OK";
122 return "NACK_ADDR";
124 return "NACK_DATA";
126 return "ARB_LOST";
128 return "TIMEOUT";
129 }
130 return "UNKNOWN";
131 }
132
133 void format(const i2c_msg_view &msg, const sink &out) const noexcept {
134 const auto &hex_digits = uppercase_hex ? detail::hex_digits_upper : detail::hex_digits_lower;
135 const bool is_10bit = (msg.flags & i2c_flags::ten_bit);
136 const bool is_read = (msg.direction == i2c_dir::read);
137
138 if (compact) {
139 // Compact inline trace: 0x68:W[75] or 0x68:R[00 1A]
140 out.write("0x");
141 detail::format_unsigned<detail::radix::hex>(out, msg.addr, uppercase_hex, is_10bit ? 3 : 2);
142 out.put(':');
143 out.put(is_read ? 'R' : 'W');
144 out.put('[');
145
146 for (size_t i = 0; i < msg.payload.size(); ++i) {
147 if (i > 0)
148 out.put(' ');
149 const uint8_t b = msg.payload[i];
150 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
151 out.put(hex_digits[(b >> 4) & 0x0F]);
152 out.put(hex_digits[b & 0x0F]);
153 RELOCO_END_UNSAFE_BUFFER_USAGE;
154 }
155 out.put(']');
156
157 if (msg.status != i2c_status::ok) {
158 out.write("!(");
159 out.write(status_name(msg.status));
160 out.put(')');
161 }
162 return;
163 }
164
165 // Verbose bus transaction format:
166 // I2C [0x68] WR (1 B) DATA: 75 -> OK
167 // I2C [0x68] RD (2 B) DATA: 04 2A -> OK
168 out.write("I2C [0x");
169 detail::format_unsigned<detail::radix::hex>(out, msg.addr, uppercase_hex, is_10bit ? 3 : 2);
170 out.put(']');
171
172 if (is_10bit) {
173 out.write(" 10b");
174 }
175
176 out.write(is_read ? " RD (" : " WR (");
177 detail::format_unsigned<detail::radix::decimal>(out, msg.payload.size(), false, 0);
178 out.write(" B)");
179
180 if (!msg.payload.empty()) {
181 out.write(" DATA: ");
182 for (size_t i = 0; i < msg.payload.size(); ++i) {
183 if (i > 0)
184 out.put(' ');
185 const uint8_t b = msg.payload[i];
186 out.put(hex_digits[(b >> 4) & 0x0F]);
187 out.put(hex_digits[b & 0x0F]);
188 }
189 }
190
191 out.write(" -> ");
192 out.write(status_name(msg.status));
193 }
194};
195
196} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
RELOCO_BEGIN_UNSAFE_CODE constexpr can_flags operator|(can_flags a, can_flags b) noexcept
Definition can.hpp:38
i2c_status
Definition i2c.hpp:29
constexpr i2c_msg_view i2c_10bit(uint16_t addr_10bit, i2c_dir dir, span< const uint8_t > data, i2c_status status=i2c_status::ok) noexcept
Creates a 10-bit I2C read or write transaction view.
Definition i2c.hpp:95
constexpr i2c_msg_view i2c_read(uint8_t addr_7bit, span< const uint8_t > data, i2c_status status=i2c_status::ok) noexcept
Creates a 7-bit I2C read transaction view.
Definition i2c.hpp:82
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
enum RELOCO_FLAG_ENUM i2c_flags
Definition i2c.hpp:31
enum RELOCO_FLAG_ENUM ten_bit
Definition i2c.hpp:33
i2c_dir
Definition i2c.hpp:27
RELOCO_END_UNSAFE_CODE constexpr bool operator&(can_flags a, can_flags b) noexcept
Definition can.hpp:43
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
enum RELOCO_FLAG_ENUM nostart
Definition i2c.hpp:34
enum RELOCO_FLAG_ENUM stop
Definition i2c.hpp:35
constexpr i2c_msg_view i2c_write(uint8_t addr_7bit, span< const uint8_t > data, i2c_status status=i2c_status::ok) noexcept
Creates a 7-bit I2C write transaction view.
Definition i2c.hpp:68
static constexpr microfmt::string_view status_name(i2c_status status) noexcept
Definition i2c.hpp:117
constexpr void parse(format_parse_context &ctx) noexcept
Definition i2c.hpp:108
void format(const i2c_msg_view &msg, const sink &out) const noexcept
Definition i2c.hpp:133
Definition microfmt.hpp:985
Non-owning I2C transfer descriptor for formatting.
Definition i2c.hpp:54