SFM Flow Meter Driver 1.0.0
API reference
Loading...
Searching...
No Matches
sfm_driver.hpp
Go to the documentation of this file.
1
26#pragma once
27
28#include "sfm_commands.hpp"
29#include "sfm_i2c_interface.hpp"
30#include "sfm_types.hpp"
31
32#include <cstddef>
33#include <cstdint>
34
35namespace sfm {
36
41template <typename I2cT>
42class Driver {
43public:
49 explicit Driver(I2cT& i2c, uint8_t address = addr::kSfm4300Default,
50 Family family = Family::Sf06) noexcept
51 : i2c_(i2c), address_(address), family_(family) {}
52
53 uint8_t Address() const noexcept { return address_; }
54 void SetAddress(uint8_t address) noexcept { address_ = address; }
55
57 Variant KnownVariant() const noexcept { return variant_; }
59 void AssumeVariant(Variant v) noexcept {
60 variant_ = v;
61 if (v != Variant::Unknown) {
62 family_ = FamilyOf(v);
63 }
64 }
65
67 Family PartFamily() const noexcept { return family_; }
68
70 Gas ActiveGas() const noexcept { return active_gas_; }
71 bool Measuring() const noexcept { return measuring_; }
73 const Scaling& ActiveScaling() const noexcept { return scaling_; }
74
75 // ------------------------------------------------------------------ identity
76
82 if (!i2c_.EnsureInitialized()) {
84 }
85 if (!WriteCommand(cmd::kReadProductIdentifier)) {
87 }
88 uint16_t words[6]{};
89 const DriverError err = ReadWords(words, 6, DriverError::BusRead);
90 if (err != DriverError::None) {
92 }
93 ProductInfo info{};
94 info.product_id = (static_cast<uint32_t>(words[0]) << 16) | words[1];
95 for (std::size_t i = 0; i < 4; ++i) {
96 info.serial[2 * i] = static_cast<uint8_t>(words[2 + i] >> 8);
97 info.serial[2 * i + 1] = static_cast<uint8_t>(words[2 + i] & 0xFFU);
98 }
99 info.variant = VariantFromProductId(info.product_id);
100 variant_ = info.variant;
101 if (variant_ != Variant::Unknown) {
102 family_ = FamilyOf(variant_);
103 }
105 }
106
107 // ------------------------------------------------------------- measurement
108
114 if (!i2c_.EnsureInitialized()) {
116 }
117 const uint16_t start = StartCommandFor(family_, g);
118 if (start == 0U) {
120 }
121 if (!WriteCommandWithArg(cmd::kReadScaleOffsetUnit, start)) {
123 }
124 /* SFx6000: the parameters sit behind an output-buffer pointer and
125 * include full-scale flow and the SEMI gas ID (reference §3.3.12). */
126 const bool sfx = (family_ == Family::Sfx6000);
127 if (sfx && !WriteCommand(cmd::kSfx6000PointerGasInfo)) {
129 }
130 uint16_t words[5]{};
131 const DriverError err = ReadWords(words, sfx ? 5 : 3, DriverError::BusRead);
132 if (err != DriverError::None) {
134 }
135 Scaling s{};
136 s.scale = static_cast<int16_t>(words[0]);
137 s.offset = static_cast<int16_t>(words[1]);
138 s.unit = words[2];
139 if (sfx) {
140 s.full_scale_raw = static_cast<int16_t>(words[3]);
141 s.gas_id = words[4];
142 }
143 s.valid = (s.scale != 0);
145 }
146
162 DriverResult<void> StartContinuous(Gas g, uint16_t o2_permille = 0) noexcept {
163 if (!i2c_.EnsureInitialized()) {
165 }
166 if (variant_ == Variant::Unknown && GasNeedsKnownFamily(g)) {
167 /* The start code for this gas means another gas on the other
168 * family: read the identifier (or AssumeVariant) first. */
170 }
171 if (!SupportsGas(variant_, g)) {
173 }
174 if (IsMixture(g) && o2_permille > 1000U) {
176 }
177 if (measuring_) {
178 const auto st = Stop();
179 if (!st.ok()) {
180 return st;
181 }
182 }
183 const auto sc = ReadScaleOffsetUnit(g);
184 if (!sc.ok()) {
185 return DriverResult<void>::failure(sc.error);
186 }
187 /* A zero scale factor cannot convert, and every SF06 data sheet fixes
188 * the flow unit at 0x0148 (slm, §4.3.6): anything else on a known SF06
189 * part means the reply is not what the conversion assumes. */
190 if (!sc.value.valid || (family_ == Family::Sf06 && variant_ != Variant::Unknown &&
191 sc.value.unit != kFlowUnitSlm20C)) {
193 }
194 const uint16_t start = StartCommandFor(family_, g);
195 const bool ok = IsMixture(g) ? WriteCommandWithArg(start, o2_permille)
196 : WriteCommand(start);
197 if (!ok) {
199 }
200 scaling_ = sc.value;
201 active_gas_ = g;
202 measuring_ = true;
205 }
206
213 if (!i2c_.EnsureInitialized()) {
215 }
216 if (!WriteCommand(cmd::kStopContinuous)) {
218 }
219 measuring_ = false;
220 i2c_.DelayMs(timing::kStopToIdleMs);
222 }
223
231 uint8_t rx[9]{};
232 if (!i2c_.Read(address_, rx, sizeof(rx))) {
234 }
235 return DecodeMeasurement(family_, scaling_, rx);
236 }
237
246 const uint8_t frame[9]) noexcept {
247 uint16_t words[3]{};
248 const DriverError err = DecodeWords(frame, 3, words);
249 if (err != DriverError::None) {
251 }
252 Measurement m{};
253 m.flow_raw = static_cast<int16_t>(words[0]);
254 m.status.raw = words[2];
255 m.flow = scaling.valid ? scaling.ToFlow(m.flow_raw) : 0.0f;
256 if (family == Family::Sfx6000) {
257 /* Word 2 is reserved on SFx6000; temperature has its own read. */
258 m.temperature_valid = false;
259 } else {
260 m.temperature_raw = static_cast<int16_t>(words[1]);
261 m.temperature_c = static_cast<float>(m.temperature_raw) / kTemperatureScalePerC;
262 }
264 }
265
272 if (family_ != Family::Sfx6000) {
274 }
275 if (!WriteCommand(cmd::kSfx6000PointerTemperature)) {
277 }
278 uint16_t word[1]{};
279 const DriverError err = ReadWords(word, 1);
280 const bool back = WriteCommand(cmd::kSfx6000PointerResult);
281 if (err != DriverError::None) {
283 }
284 if (!back) {
286 }
287 return DriverResult<float>::success(static_cast<float>(static_cast<int16_t>(word[0])) /
289 }
290
293 uint16_t words[1]{};
294 const DriverError err = ReadWords(words, 1);
295 if (err != DriverError::None) {
297 }
299 scaling_.valid ? scaling_.ToFlow(static_cast<int16_t>(words[0])) : 0.0f);
300 }
301
306 DriverResult<void> ConfigureAveraging(uint16_t window) noexcept {
307 if (family_ == Family::Sfx6000) {
309 }
310 if (window > timing::kMaxAveragingWindow) {
312 }
313 if (!i2c_.EnsureInitialized()) {
315 }
316 return WriteCommandWithArg(cmd::kConfigureAveraging, window)
319 }
320
325 DriverResult<void> UpdateConcentration(uint16_t o2_permille) noexcept {
326 if (o2_permille > 1000U) {
328 }
329 if (!i2c_.EnsureInitialized()) {
331 }
332 if (!WriteCommandWithArg(cmd::kUpdateConcentrationSet, o2_permille)) {
334 }
335 if (!WriteCommand(cmd::kUpdateConcentrationActivate)) {
337 }
339 }
340
341 // -------------------------------------------------------------- power / reset
342
345 if (family_ == Family::Sfx6000) {
347 }
348 if (!i2c_.EnsureInitialized()) {
350 }
351 return WriteCommand(cmd::kEnterSleep) ? DriverResult<void>::success()
353 }
354
360 if (family_ == Family::Sfx6000) {
362 }
363 if (!i2c_.EnsureInitialized()) {
365 }
366 const uint8_t dummy = 0x00;
367 (void)i2c_.Write(address_, &dummy, 1);
368 for (int i = 0; i < 4; ++i) {
369 i2c_.DelayMs(5);
370 if (i2c_.Write(address_, &dummy, 0)) {
372 }
373 }
375 }
376
382 if (!i2c_.EnsureInitialized()) {
384 }
385 if (!i2c_.WriteGeneralCall(cmd::kGeneralCallReset)) {
387 }
388 measuring_ = false;
389 scaling_ = Scaling{};
390 i2c_.DelayMs(family_ == Family::Sfx6000 ? timing::kSfx6000SoftResetMs
393 }
394
395 // ------------------------------------------------------------------ framing
396
398 static void EncodeWord(uint16_t word, uint8_t out[3]) noexcept {
399 out[0] = static_cast<uint8_t>(word >> 8);
400 out[1] = static_cast<uint8_t>(word & 0xFFU);
401 out[2] = Crc8(out, 2);
402 }
403
405 static DriverError DecodeWords(const uint8_t* in, std::size_t count, uint16_t* out) noexcept {
406 for (std::size_t i = 0; i < count; ++i) {
407 const uint8_t* p = in + 3 * i;
408 if (Crc8(p, 2) != p[2]) {
409 return DriverError::Crc;
410 }
411 out[i] = static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8) | p[1]);
412 }
413 return DriverError::None;
414 }
415
416private:
417 bool WriteCommand(uint16_t command) noexcept {
418 const uint8_t tx[2] = {static_cast<uint8_t>(command >> 8),
419 static_cast<uint8_t>(command & 0xFFU)};
420 return i2c_.Write(address_, tx, sizeof(tx));
421 }
422
423 bool WriteCommandWithArg(uint16_t command, uint16_t arg) noexcept {
424 uint8_t tx[5];
425 tx[0] = static_cast<uint8_t>(command >> 8);
426 tx[1] = static_cast<uint8_t>(command & 0xFFU);
427 EncodeWord(arg, &tx[2]);
428 return i2c_.Write(address_, tx, sizeof(tx));
429 }
430
436 DriverError ReadWords(uint16_t* out, std::size_t count,
437 DriverError on_nack = DriverError::NoData) noexcept {
438 if (count == 0 || count > 6) {
440 }
441 uint8_t rx[18]{};
442 if (!i2c_.Read(address_, rx, 3 * count)) {
443 return on_nack;
444 }
445 return DecodeWords(rx, count, out);
446 }
447
448 I2cT& i2c_;
449 uint8_t address_;
450 Family family_{Family::Sf06};
451 Variant variant_{Variant::Unknown};
452 Gas active_gas_{Gas::Air};
453 Scaling scaling_{};
454 bool measuring_{false};
455};
456
457} // namespace sfm
SF06 flow-meter client.
Definition sfm_driver.hpp:42
Gas ActiveGas() const noexcept
Gas selected by the last successful StartContinuous().
Definition sfm_driver.hpp:70
Variant KnownVariant() const noexcept
Variant learned from the last successful ReadProductIdentifier().
Definition sfm_driver.hpp:57
DriverResult< void > ExitSleep() noexcept
Exit sleep: any write header wakes the part; it does not ACK (§4.3.8). Polls up to ~20 ms until the a...
Definition sfm_driver.hpp:359
DriverResult< void > UpdateConcentration(uint16_t o2_permille) noexcept
Update the O2 fraction of a running mixture measurement (§4.3.3).
Definition sfm_driver.hpp:325
DriverResult< ProductInfo > ReadProductIdentifier() noexcept
Read product identifier and serial number (§4.3.9).
Definition sfm_driver.hpp:81
DriverResult< void > EnterSleep() noexcept
Enter sleep (idle mode only, §4.3.8).
Definition sfm_driver.hpp:344
DriverResult< float > ReadFlowOnly() noexcept
Flow-only read (2 words + CRC) for the tightest loop; status not refreshed.
Definition sfm_driver.hpp:292
DriverResult< void > SoftReset() noexcept
I2C general-call soft reset (§4.3.7): 0x06 to address 0x00, then ~20 ms.
Definition sfm_driver.hpp:381
static void EncodeWord(uint16_t word, uint8_t out[3]) noexcept
Encode a 16-bit word + CRC-8 into 3 bytes.
Definition sfm_driver.hpp:398
bool Measuring() const noexcept
Definition sfm_driver.hpp:71
DriverResult< Measurement > ReadMeasurement() noexcept
Read one flow / temperature / status frame (§4.3.1).
Definition sfm_driver.hpp:230
DriverResult< void > Stop() noexcept
Stop continuous measurement → idle (§4.3.4).
Definition sfm_driver.hpp:212
void AssumeVariant(Variant v) noexcept
Let the caller assert a variant when the identifier cannot be read (already measuring).
Definition sfm_driver.hpp:59
const Scaling & ActiveScaling() const noexcept
Scaling captured for the active gas.
Definition sfm_driver.hpp:73
void SetAddress(uint8_t address) noexcept
Definition sfm_driver.hpp:54
DriverResult< Scaling > ReadScaleOffsetUnit(Gas g) noexcept
Query scale factor, offset and unit for a gas table (§4.3.6).
Definition sfm_driver.hpp:113
DriverResult< void > ConfigureAveraging(uint16_t window) noexcept
Configure on-sensor averaging (§4.3.5).
Definition sfm_driver.hpp:306
DriverResult< float > ReadTemperature() noexcept
SFx6000: read the flow-chip temperature while measuring (pointer 0xE102 → one word → pointer back to ...
Definition sfm_driver.hpp:271
static DriverError DecodeWords(const uint8_t *in, std::size_t count, uint16_t *out) noexcept
Decode count (word, crc) triplets; returns Crc on the first mismatch.
Definition sfm_driver.hpp:405
Family PartFamily() const noexcept
Family in use (from the identifier, AssumeVariant, or the constructor).
Definition sfm_driver.hpp:67
DriverResult< void > StartContinuous(Gas g, uint16_t o2_permille=0) noexcept
Start continuous measurement for g (§4.3.1).
Definition sfm_driver.hpp:162
static DriverResult< Measurement > DecodeMeasurement(Family family, const Scaling &scaling, const uint8_t frame[9]) noexcept
Decode one 9-byte continuous-mode frame (3 × word + CRC).
Definition sfm_driver.hpp:245
Driver(I2cT &i2c, uint8_t address=addr::kSfm4300Default, Family family=Family::Sf06) noexcept
Definition sfm_driver.hpp:49
uint8_t Address() const noexcept
Definition sfm_driver.hpp:53
constexpr uint8_t kSfm4300Default
ADDR to GND or floating.
Definition sfm_commands.hpp:26
constexpr uint16_t kStopContinuous
Definition sfm_commands.hpp:69
constexpr uint16_t kSfx6000PointerGasInfo
after kReadScaleOffsetUnit
Definition sfm_commands.hpp:64
constexpr uint16_t kConfigureAveraging
argument: N (0 = avg-until-read)
Definition sfm_commands.hpp:70
constexpr uint16_t kEnterSleep
Definition sfm_commands.hpp:72
constexpr uint16_t kUpdateConcentrationSet
argument: ‰
Definition sfm_commands.hpp:67
constexpr uint16_t kUpdateConcentrationActivate
Definition sfm_commands.hpp:68
constexpr uint8_t kGeneralCallReset
single byte to address 0x00
Definition sfm_commands.hpp:74
constexpr uint16_t kReadProductIdentifier
Definition sfm_commands.hpp:73
constexpr uint16_t kReadScaleOffsetUnit
argument: start command code
Definition sfm_commands.hpp:71
constexpr uint16_t kSfx6000PointerResult
back to flow / reserved / status
Definition sfm_commands.hpp:66
constexpr uint16_t kSfx6000PointerTemperature
while measuring (product ID when idle)
Definition sfm_commands.hpp:65
constexpr uint32_t kSfx6000SoftResetMs
interface reference §1.2
Definition sfm_commands.hpp:87
constexpr uint32_t kFirstResultAfterStartMs
first reading valid after start
Definition sfm_commands.hpp:79
constexpr uint32_t kStopToIdleMs
kStopToIdleUs rounded up for DelayMs()
Definition sfm_commands.hpp:85
constexpr uint16_t kMaxAveragingWindow
Definition sfm_commands.hpp:86
constexpr uint32_t kSoftResetMs
typ. 16 ms, max 20 ms
Definition sfm_commands.hpp:81
Definition sfm.hpp:20
constexpr Family FamilyOf(Variant v) noexcept
Family of a known variant (Sf06 for Unknown — the caller's assumption).
Definition sfm_types.hpp:213
constexpr uint16_t StartCommandFor(Family family, Gas g) noexcept
Start-measurement command code for g on family.
Definition sfm_types.hpp:126
DriverError
Driver-level error codes (stable for logging / telemetry).
Definition sfm_types.hpp:28
@ BusWrite
command write NACKed / bus error
@ VariantUnknown
gas needs a known part family (identity not read)
@ Crc
CRC-8 mismatch on a received word.
@ InvalidParameter
bad argument (e.g. averaging window > 128)
@ BusRead
command reply (identifier, scaling) NACKed / bus error
@ UnexpectedUnit
scaling unusable: scale 0, or unit ≠ 0x0148 on a known SF06 part
@ NotInitialized
transport EnsureInitialized() failed
@ NotSupported
transport lacks the feature (e.g. general call)
@ UnsupportedGas
gas / mixture not calibrated on this variant
@ NoData
result-buffer read NACKed — no fresh sample yet / not measuring
constexpr bool SupportsGas(Variant v, Gas g) noexcept
Whether a variant carries a factory calibration for g.
Definition sfm_types.hpp:288
Gas
Calibrated gas / gas-mixture lookup table selector (family-neutral).
Definition sfm_types.hpp:91
@ Air
SF06 Gas 1 · SFx6000 Gas 1.
Family
Part family: decides command meaning, frame layout and optional features.
Definition sfm_types.hpp:81
@ Sf06
SFM4300, SFM3003/3013/3019/3119.
@ Sfx6000
SFM6000D (SFC6000D controllers share the interface)
constexpr bool IsMixture(Gas g) noexcept
Definition sfm_types.hpp:161
constexpr Variant VariantFromProductId(uint32_t product_id) noexcept
Decode a 32-bit product identifier.
Definition sfm_types.hpp:227
constexpr bool GasNeedsKnownFamily(Gas g) noexcept
True when g's start code differs between families (needs a known variant).
Definition sfm_types.hpp:318
constexpr uint8_t Crc8(const uint8_t *data, std::size_t len) noexcept
CRC-8 per Sensirion SF06 (poly 0x31, init 0xFF, no reflection/xorout).
Definition sfm_commands.hpp:103
constexpr float kTemperatureScalePerC
Temperature scale (data sheet Tables 15/16: scale 200 per °C, offset 0).
Definition sfm_commands.hpp:95
Variant
Part variant decoded from the product identifier.
Definition sfm_types.hpp:171
constexpr uint16_t kFlowUnitSlm20C
Fixed flow-unit code returned by the SFM4300 (slm at 20 °C, 1013.25 mbar).
Definition sfm_commands.hpp:92
I2C command codes, addresses, timing and CRC-8 for Sensirion flow meters: the SF06 family (SFM4300 da...
CRTP byte transport for Sensirion SF06-family flow meters (SFM4300, …).
Flow-meter types: families, gases, variants, readings, status word, results.
Definition sfm_types.hpp:60
static constexpr DriverResult failure(DriverError e) noexcept
Definition sfm_types.hpp:68
static constexpr DriverResult success(T v) noexcept
Definition sfm_types.hpp:67
One continuous-mode sample (data sheet §4.3.1 read frame).
Definition sfm_types.hpp:436
int16_t flow_raw
Definition sfm_types.hpp:437
Product identifier + 8-byte serial number (data sheet §4.3.9).
Definition sfm_types.hpp:323
uint32_t product_id
Definition sfm_types.hpp:324
Scale factor / offset / unit for one gas lookup table (data sheet §4.3.6).
Definition sfm_types.hpp:330
int16_t scale
counts per flow unit (SFM4300: 1000 per slm)
Definition sfm_types.hpp:331
constexpr float ToFlow(int16_t raw) const noexcept
raw → flow in the device unit (slm for SFM4300).
Definition sfm_types.hpp:344
bool valid
Definition sfm_types.hpp:336