Compiler-verification of pin assignments¶
This project uses a novel approach to selecting microprocessor peripherals from their pin numbers.
A typical microprocessor has multiple different features available on each pin.
However, very rarely is every feature available on every pin, and the mapping
between these features and their pins is only discoverable through a very long
table in the data sheet. For instance, we might find that OC1
is available
on pin 3
.
The result is typically that the source code does not contain any reference to
pin 3
at all, and only refers to the underlying hardare, OC1
. This is
bad for maintainability, as it tells the programmer nothing about how to check
the wiring, or what to change should the wiring change.
Obviously then, we want a function to convert pin numbers into their corresponding hardware names, and this is not a hard task. But this creates a new danger - it encourages the programmer to change pin a pin to one that no longer supports the features needed! This kind of mistake can be caught at run-time, but when programming takes a few minutes, or getting feedback is difficult, this is not acceptable.
C++11 introduces a new keyword called constexpr. When attached to a variable, this tells the compiler that its value must be calculated at compile-time. The trick then, is to produce a function that for a valid pin, is calculable at compilation-time, and for an invalid pin, is not calculable at compilation-time. We capitalize on the fact that the compiler only cares about the compile-time-calculability of the code-path it takes:
// note: no constexpr
int failure(const char* msg) { report_error(msg); while(1); }
constexpr int must_be_even(int i) {
return i % 2 == 0
? i // calculable at compile-time
: failure("Not even"); // not calculable at compile-time
}
This almost works as intended:
constexpr int ok = must_be_even(2);
constexpr int compile_error = must_be_even(3);
int ok = must_be_even(2);
int runtime_error = must_be_even(3);
There is a isocpp paper [N3583] and a follow-up paper that details possible language changes and workaround to the problem of this third line. The solution opted for was to encourage writing these as:
constexpr int ok = must_be_even<2>();
constexpr int compile_error = must_be_even<3>();
int compile_error = must_be_even<2>();
int compile_error = must_be_even<3>();
[N3583] | Scott Schurr, Exploring constexpr at Runtime, 2013. https://isocpp.org/files/papers/n3583.pdf |
API documentation¶
-
namespace
io
¶ Devices
These variables just turn the long
#define
d names into C++ references of the approapriate types-
constexpr p32_oc &
oc1
= *reinterpret_cast<p32_oc*>(_OCMP1_BASE_ADDRESS)¶
-
constexpr p32_oc &
oc2
= *reinterpret_cast<p32_oc*>(_OCMP2_BASE_ADDRESS)¶
-
constexpr p32_oc &
oc3
= *reinterpret_cast<p32_oc*>(_OCMP3_BASE_ADDRESS)¶
-
constexpr p32_oc &
oc4
= *reinterpret_cast<p32_oc*>(_OCMP4_BASE_ADDRESS)¶
-
constexpr p32_timer &
tmr1
= *reinterpret_cast<p32_timer*>(_TMR1_BASE_ADDRESS)¶
-
constexpr p32_timer &
tmr2
= *reinterpret_cast<p32_timer*>(_TMR2_BASE_ADDRESS)¶
-
constexpr p32_timer &
tmr3
= *reinterpret_cast<p32_timer*>(_TMR3_BASE_ADDRESS)¶
-
constexpr p32_timer &
tmr4
= *reinterpret_cast<p32_timer*>(_TMR4_BASE_ADDRESS)¶
-
constexpr p32_timer &
tmr5
= *reinterpret_cast<p32_timer*>(_TMR5_BASE_ADDRESS)¶
-
constexpr p32_i2c &
i2c1
= *reinterpret_cast<p32_i2c*>(_I2C1_BASE_ADDRESS)¶
-
constexpr p32_i2c &
i2c2
= *reinterpret_cast<p32_i2c*>(_I2C2_BASE_ADDRESS)¶
-
constexpr p32_cn &
cn
= *reinterpret_cast<p32_cn*>(_CN_BASE_ADDRESS)¶
Helpers
These functions allow conversion between devices and relevant configuation needed to use them elsewhere. These prevent a device having to be referred to by name in more than one place.
These should all be evaluated at compile time!
-
constexpr int
irq_for
(const p32_timer &tmr)¶ Get the interrupt bit number for a given timer.
-
constexpr int
irq_for
(const p32_cn &cn)¶ Get the interrupt bit number for the change notice hardware.
-
constexpr int
vector_for
(const p32_timer &tmr)¶ Get the interrupt vector number for a given timer.
-
constexpr int
vector_for
(const p32_cn &cn)¶ Get the interrupt vector number for the change notice hardare.
-
constexpr p32_timer &
timer_for
(uint8_t pin)¶ Get the timer connected to a given CK (clock) pin.
-
constexpr p32_oc &
oc_for
(uint8_t pin)¶ Get the output compare (PWM) connected to a given pin.
-
constexpr p32_i2c &
i2c_for
(uint8_t scl, uint8_t sda)¶ Get the I2C connected to a given pair of pins.
Compile-time Helpers
Like the above functions, but with arguments re-expresed as template parameters to force errors at compile time. These make it impossible to choose an invalid pin.
-
template <uint8_t pin>
p32_timer &timer_for
()¶
-
template <uint8_t pin>
p32_oc &oc_for
()¶
-
template <uint8_t scl, uint8_t sda>
p32_i2c &i2c_for
()¶
Functions
-
template <typename T>
Tfailed
(const char *)¶ Helper function for when an invalid argument is supplied.
-
constexpr p32_oc &