allocating the same pin or counter twice
how do you allocate pins?
static uint32_t port_pin_in_use[port] ;
void allocate_port_pin(uint8_t port_pin)
{
uint8_t port = port_pin >> 5 ;
uint8_t pin = port & 0b11111 ;
if (port_pin_in_use[port] & 0x1 << pin)
{
while (1)
{
; // this is the second time you are using this pin
}
}
else port_pin_in_use[port] |= 0x1 << pin ;
}
The port pin argument is simply an enumeration of PA00, PA01.......PB00, PB01.... to numbers starting at 0
Yeah it's not possible to tell at compile time how many times a given function is called. static_assert only works expressions that can be evaluated at compile time. I'm not sure how you expected that to work.
grep -c 'allocate_port_pin\(PA00\)' *.c
and check for greater than 1 is probably about the best you can do. But it for sure can have false negatives and positives.
Here is a no-runtime-overhead approach that works:
Use a specific section for the used pin numbers, and a preprocessor macro to declare the pin allocated:
#define PIN_SECTION "pins"
#define MERGE4_(a,b,c,d) a ## b ## c ## d
#define MERGE4(a,b,c,d) MERGE4_(a,b,c,d)
#define NAMELINE(prefix) MERGE4(__, prefix, _, __LINE__)
#define ALLOCATE_PIN(pin, message) \
static const char NAMELINE(pin_alloc) \
__attribute__((section (PIN_SECTION), used)) = pin
int main(void)
{
ALLOCATE_PIN(1, "First example");
ALLOCATE_PIN(2, "Second example");
}
ALLOCATE_PIN(3, "Third example");
The message is not recorded, it is only for the human programmers. An allocation can occur in any scope, even inside functions, even inside a loop iteration.
Then, in your build, when all .o files have been compiled, you collect their pin allocations, and check if any pin is allocated more than once anywhere. Using Bash, od, tr, awk, and objcopy (included as part of the target binutils toolchain):
for file in *.o ; do objcopy -O binary -j pins "$file" /dev/stdout ; done | od -A none -t d1 | tr '\t\n\v\f\r ' '\n\n\n\n\n\n' | awk 'BEGIN { split("", pin) } NF==1 { pin[$1]++ } END { status=0; for (p in pin) if (pin[p] > 1) { status=1 ; printf "Pin %d (0x%02x) used %d times!\n", p, pin[p], pin[p] } exit(status) }'
(This works on all targets and toolchains that use ELF object files, it is not Linux/BSD specific at all; I just used the tools I currently have for the latter example, because it was the quickest way.)
As is, the above is just the proof of concept, allowing the macro use anywhere, but not recording the message. There is no runtime overhead at all, as long as the "pins" section is discarded from the final binary. The same approach can be used for resource allocation, even external memory ranges in EEPROMs and PSRAMs not mapped to the device address space, of course, just using a fixed-size structure instead of a single unsigned char.
With a bit more complex example, using a fixed-size structure to include the pin, the pin name stringified, the file name (__FILE__ macro), the line number (__LINE__), and the message, a slightly more complex check can describe the exact conflicting allocations, with absolutely no runtime overhead. The only overhead is increased build-time object file size, as they grow larger by the structures, but the final binary is not affected at all. I can write such a checker (reading objcopy output, or internally invoking objcopy) in C or Python 3 for example, but note that this would be run on the development machine at build time, not on the target device. Python 3 might be more portable, as I prefer C99 + POSIX interfaces. (I wanted to try keep this post short, you see. :-[)
If you want to actually use the latter, I can create a larger example, and a suitable object file checker for you. It is not complicated at all, as you can put everything behind a single header file, and if you run the checker as part of the build process using make or cmake or derivatives, they will abort the build if the checker returns nonzero.
#define ALLOCATE_PIN(pin, message) \
static const char NAMELINE(pin_alloc) \
__attribute__((section (PIN_SECTION), used)) = pin
Brilliant approach!
You take advantage of the sections, which due to how the linker script is made can be "skimmed" (filtered) in the final executable.
I have this interesting hack that I've used. It puts functions that are defined with the "error" attribute in places where the compiler will eliminate them (due to optimization) when there is no problem.
boolean NonConstantUsed( void ) __attribute__ (( error("") ));
void LTO_Not_Enabled(void) __attribute__ (( error("") ));
#define digitalWriteFast(pin, val) \
if (__builtin_constant_p(pin)) {_dwfast(pin, val);} else {NonConstantUsed();}
static inline __attribute__((always_inline)) void _dwfast(int pin, int val) {
uint8_t mask = digital_pin_to_bit_mask_PGM[pin];
uint8_t port = digital_pin_to_port_PGM[pin];
volatile uint8_t *outport = (volatile uint8_t*)port_to_output_PGM[port];
if (digital_pin_to_bit_mask_PGM[0] == 0x99) // Never true
LTO_Not_Enabled();
if (val) {
*outport |= mask;
} else {
*outport &= ~mask;
}
}
The if (digital_pin_to_bit_mask_PGM[0] == 0x99) // Never true is especially interesting. In the case where the array is available at compile time, the "if" can be evaluated at compile time as well, so there is no need to call the function. If it's external, the compiler will try to insert the function call, and cause the error.
Another possibility is to use inline asm.
asm (at least with the gnu assembler) has "deeper visibility" into things like existing symbols and their values.
For instance:
int main(void)
{
asm(".ifdef main\n"
".warning \"main is already defined, from ASM\"\n"
".endif\n");
#ifdef main
#warning main is already defined, from C
#endif
}
The interesting ones the Linux kernel has historically used:
#define BUILD_BUG_ON_ZERO(expr) (sizeof(struct { int:(-!!(expr)); }))
which causes a build-time failure if expr is true or nonzero. This relies on C99. When expr is zero or false, it calculates the size of a structure with an unnamed bitfield of zero width (which indicates no more bitfields in the current unit), so normally the expression evaluates to zero. However, when expr is nonzero, it tries to calculate the size of a structure with an anonymous bitfield of size -1 bits.
The _ZERO suffix is not an error: it does not mean that this does a build bug when the expression is zero, it refers to the fact that normally, this expression returns zero.
#define BUILD_BUG_ON(expr) ((void)sizeof(char[1 - 2*!!(expr)]))
also causes a build-time failure if expr is nonzero. When expr is zero or false, it is simply an unused sizeof expression of a one-char array. When expr is true or nonzero, it becomes an unused sizeof expression of an array with -1 chars, and thus should lead to a compile-time error.
#define BUILD_BUG_ON_INVALID(expr) ((void)(sizeof((long)(expr))))
This verifies that expr is valid, without evaluating any side effects in expr. (For example, BUILD_BUG_ON_INVALID(x++) checks that x is a valid variable, but does not increment it.)
In any case, I do recommend using static_assert(expr, message_string), with one of the header files containing
#define static_assert(expr, msg) _Static_assert(expr, msg)
and expand on that when you encounter compilers that do not support it. Each compiler does provide identifiable pre-defined macros (https://sourceforge.net/p/predef/wiki/Home/) one can #ifdef about, and implement a suitable workaround. Even sdcc has supported _Static_assert since version 3.3.0 (released in 2013).
If you want to future-proof that, use#if !defined(static_assert)
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#define static_assert(expr, msg) static_assert(expr, msg)
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define static_assert(expr, msg) _Static_assert(expr, msg)
#else
#define static_assert(expr, msg) ((void)sizeof(char[1 - 2*!(expr)]))
#endif
#endif /* !defined(static_assert) */
which uses the __STDC_VERSION__ pre-defined macro the compiler is supposed to set. If it is already a macro, it does not redefine it. If the compiler advertises itself as a C23 one, then static_assert is used. If not, but the compiler advertises itself as a C11 one, _Static_assert is used. Otherwise, it falls back to evaluating the size of an array of chars with one element if expr is true, and -1 elements if expr is false (losing the message, though).
That assumes C23 will have __STDC_VERSION__ == 202311L.