Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ if(ESP_PLATFORM)
endif()

project(electro
VERSION 0.1.0
VERSION 0.2.0
DESCRIPTION "Type-safe electrical units library modeled after std::chrono"
HOMEPAGE_URL "https://github.com/cleishm/electro-cpp"
LANGUAGES CXX
Expand Down
126 changes: 122 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ A type-safe, header-only C++20 library for electrical units, modeled after `std:
- **Standard SI multiples**: microvolts through kilovolts, milliohms through gigaohms, picofarads through farads, and more
- **Cross-quantity arithmetic** following circuit theory: Ohm's law (`I * R = V`), power (`V * I = P`), and friends
- **`std::chrono` interop**: `mA x hours = mAh`, `W x hours = Wh`, `Ω x F = RC time constant` (as a `std::chrono::duration`)
- **Decibels**: `dB` gains and `dBm`/`dBW`/`dBµV` levels, related as `duration` is to `time_point`, so that `dBm + dBm` is a compile error
- **Integer and floating-point representations** for exact or fractional precision
- **Lossless implicit conversions** (lossy conversions require explicit casts)
- **User-defined literals** for concise notation (`3300_mV`, `10_kΩ`, `2000_mAh`)
- **User-defined literals** for concise notation (`3300_mV`, `10_kΩ`, `2000_mAh`, `-73_dBm`)
- **`std::format` support** (when available)
- **Fully constexpr** - all operations can be evaluated at compile time
- **Fully constexpr** - all operations can be evaluated at compile time (except the dB ↔ linear conversions, which need `std::log10`/`std::pow`)

## Requirements

Expand All @@ -30,7 +31,7 @@ include(FetchContent)
FetchContent_Declare(
electro
GIT_REPOSITORY https://github.com/cleishm/electro-cpp.git
GIT_TAG v0.1.0
GIT_TAG v0.2.0
)
FetchContent_MakeAvailable(electro)

Expand Down Expand Up @@ -120,6 +121,93 @@ integer division, so choose operand precisions accordingly:
`5_V / 3_Ohm` is `1 A`, while `5000_mV / 3_Ohm` is `1666 mA`. Floating-point
representations (e.g. `voltage<double>`) avoid truncation entirely.

## Decibels and Levels

Decibels live in a separate, opt-in header:

```cpp
#include <electro/decibel> // or <electro/decibel.hpp>
```

They come in two kinds, and conflating them is the classic source of bugs. This
library keeps them apart using the same distinction `std::chrono` draws between
a `duration` and a `time_point`:

| | | Closed under |
|---|---|---|
| **gain** (`dB`) | a dimensionless *ratio* — the `duration` analog | `+`, `-`, `* scalar` |
| **level** (`dBm`, `dBW`, `dBµV`) | an *absolute* point on a log scale — the `time_point` analog | `level ± gain`, `level - level → gain` |

```cpp
auto tx = 20_dBm;
auto eirp = tx + 6_dB - 2_dB; // level ± gain -> level (24 dBm)
auto rx = eirp - 90_dB; // path loss (-66 dBm)
auto margin = rx - -100_dBm; // level - level -> gain (34 dB)

// tx + tx; // error: adding two absolute levels is meaningless
// tx * 2; // error: scaling an absolute level is meaningless
// tx + 10_dBW; // error: references do not mix
```

That last group matters: a plain `dBm` unit tag would accept all three and
silently compute nonsense (`10 dBm + 10 dBm` is `13.01 dBm`, not `20 dBm`).
Combining the powers of two uncorrelated signals is a named function, never
`operator+`:

```cpp
add_powers(1000_cdBm, 1000_cdBm); // 13.01 dBm
```

### Crossing into the linear domain

Conversions are explicit, named functions rather than constructors: they are
non-`constexpr`, nonlinear, and have domain errors a constructor cannot report.
A level converts at its own reference precision, so `dBm` yields milliwatts:

```cpp
to_linear(30_dBm); // 1000.0 mW, as power<double, std::milli>
quantity_cast<watts>(to_linear(30_dBm)); // 1 W
to_level<dbm>(milliwatts(1)); // 0 dBm
to_level<dbm>(watts(1)); // 30 dBm
```

A zero quantity has no finite logarithm and maps to `dbm::min()`; a negative one
asserts. Values are rounded to nearest, not truncated.

### Power and field references

A reference records whether its linear quantity is a *power* (`10·log10`) or a
*field* such as voltage (`20·log10`), so callers never have to remember which
factor applies:

```cpp
to_level<dbuv>(volts(1)); // 120 dBµV, not 60
```

For the same reason, a bare `dB` value cannot be turned into a linear ratio
unambiguously — 3 dB is a factor of 2 in power but ~1.41 in amplitude — so the
two conversions are separately named:

```cpp
power_ratio(3_dB); // ~1.995
amplitude_ratio(6_dB); // ~1.995
```

`add_powers` is available only for power references; for a field reference,
summing linear amplitudes would model coherent addition instead, which is a
different operation with a different answer.

### Precision

Integer decibels at 1 dB resolution are coarse. Use a finer precision or a
floating-point representation:

```cpp
centi_dbm fine{-7350}; // -73.50 dBm
dbm_level<double> exact{-73.5};
to_string(fine); // "-73.50dBm"
```

## Quantity Types

All standard aliases use `int64_t` representation. Each quantity also has an
Expand All @@ -137,6 +225,20 @@ alias template for custom representations and precisions, e.g.
| Capacitance (F) | `capacitance<Rep, Precision>` | `picofarads`, `nanofarads`, `microfarads`, `millifarads`, `farads` |
| Inductance (H) | `inductance<Rep, Precision>` | `nanohenries`, `microhenries`, `millihenries`, `henries` |

### Decibel Types

From `<electro/decibel>`. Levels are `level<Reference, Rep, Precision>`; gains
are ordinary quantities of `decibel_unit`.

| Kind | Alias template | Standard aliases |
|------|----------------|------------------|
| Gain (dB) | `gain<Rep, Precision>` | `decibels`, `centidecibels`, `millidecibels` |
| Level vs 1 mW | `dbm_level<Rep, Precision>` | `dbm`, `centi_dbm` |
| Level vs 1 W | `dbw_level<Rep, Precision>` | `dbw` |
| Level vs 1 V | `dbv_level<Rep, Precision>` | `dbv` |
| Level vs 1 mV | `dbmv_level<Rep, Precision>` | `dbmv` |
| Level vs 1 µV | `dbuv_level<Rep, Precision>` | `dbuv`, `centi_dbuv` |

## Literals

| Quantity | Literals |
Expand All @@ -149,6 +251,13 @@ alias template for custom representations and precisions, e.g.
| Energy | `_mJ`, `_J`, `_kJ`, `_Wh`, `_kWh` |
| Capacitance | `_pF`, `_nF`, `_uF`, `_F` |
| Inductance | `_nH`, `_uH`, `_mH`, `_H` |
| Gain | `_dB`, `_cdB` (0.01 dB steps) |
| Level | `_dBm`, `_cdBm`, `_dBW`, `_dBV`, `_dBmV`, `_dBuV` (also `_dBµV`) |

Literals are integral, so fractional values use the `centi` variants
(`1301_cdBm` is 13.01 dBm) or an explicit floating-point type. Negative levels
such as `-73_dBm` work because `level` provides a unary `operator-`, which
reflects the level about its reference.

## Conversions

Expand All @@ -167,14 +276,23 @@ auto r = round<volts>(millivolts(5500)); // 6 V (round half to even)
auto c = quantity_cast<ampere_hours>(coulombs(7200)); // 2 Ah
```

Levels follow the same rules, via `level_cast` and the same `floor`, `ceil` and
`round` overloads:

```cpp
centi_dbm fine = 20_dBm; // implicit: 2000 (20.00 dBm)
dbm coarse = dbm(centi_dbm(1301)); // explicit: truncates to 13 dBm
auto n = round<dbm>(centi_dbm(1350)); // 14 dBm
```

## ESP-IDF

The library can be used as an ESP-IDF component. Add it to your project's
`idf_component.yml`:

```yaml
dependencies:
cleishm/electro: "^0.1.0"
cleishm/electro: "^0.2.0"
```

`std::format` support is controlled via `CONFIG_ELECTRO_STD_FORMAT` in
Expand Down
2 changes: 1 addition & 1 deletion idf_component.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "0.1.0"
version: "0.2.0"
description: "Type-safe electrical units library modeled after std::chrono"
url: "https://github.com/cleishm/electro-cpp"
repository: "https://github.com/cleishm/electro-cpp.git"
Expand Down
2 changes: 2 additions & 0 deletions include/electro/decibel
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#pragma once
#include <electro/decibel.hpp>
Loading
Loading