9 Essential C Utility Snippets Every Embedded Engineer Should Know
This article presents nine powerful C utility code examples—including a circular buffer, custom assertions, bit reversal, fixed‑point arithmetic, endianness conversion, bit masks, timer handling, binary search, and a simple bitset—explaining their purpose and demonstrating how they can improve performance and maintainability in embedded system development.
1. Circular Buffer
A circular buffer implements a fixed‑size FIFO queue without moving data. It uses head and tail indices that wrap around using modulo arithmetic, making push and pop operations O(1). This pattern is common for UART receive buffers or any streaming data source.
typedef struct {
int buffer[SIZE]; // storage array, SIZE must be a power of two for optimal modulo
int head; // index of next write position
int tail; // index of next read position
int count; // number of elements currently stored
} CircularBuffer;
void push(CircularBuffer *cb, int data) {
if (cb->count < SIZE) {
cb->buffer[cb->head] = data;
cb->head = (cb->head + 1) % SIZE;
cb->count++;
}
// else: buffer full – could handle overflow here
}
int pop(CircularBuffer *cb) {
if (cb->count > 0) {
int data = cb->buffer[cb->tail];
cb->tail = (cb->tail + 1) % SIZE;
cb->count--;
return data;
}
return -1; // sentinel for empty buffer
}2. Assertion Macro
The macro provides a lightweight runtime check that is compiled out when NDEBUG is defined. When a condition fails, assert_failed prints the source file and line number, which aids debugging in embedded builds where standard assert may be unavailable.
#define assert(expression) ((void)0) // default no‑op
#ifndef NDEBUG
#undef assert
#define assert(expression) ((expression) ? (void)0 : assert_failed(__FILE__, __LINE__))
#endif
void assert_failed(const char *file, int line) {
printf("Assertion failed at %s:%d
", file, line);
// optional: halt CPU, blink LED, or log to serial port
}3. Bit Reversal
This function reverses the order of bits in an unsigned integer. It is useful for protocols that transmit least‑significant bits first or for hardware peripherals that expect reversed bit patterns.
unsigned int reverse_bits(unsigned int num) {
unsigned int numOfBits = sizeof(num) * 8;
unsigned int reverseNum = 0;
for (unsigned int i = 0; i < numOfBits; i++) {
if (num & (1u << i)) {
reverseNum |= (1u << ((numOfBits - 1) - i));
}
}
return reverseNum;
}4. Fixed‑Point Arithmetic
Fixed‑point representation stores fractional values as integers scaled by 2^FIXED_SHIFT. The macros convert between floating‑point and fixed‑point, and fixed_multiply performs multiplication without overflow by using a wider intermediate type.
#include <stdint.h>
typedef int16_t fixed_t; // 16‑bit signed fixed‑point
#define FIXED_SHIFT 8 // 8 fractional bits (Q8.8 format)
#define FLOAT_TO_FIXED(f) ((fixed_t)((f) * (1 << FIXED_SHIFT)))
#define FIXED_TO_FLOAT(f) ((float)(f) / (1 << FIXED_SHIFT))
fixed_t fixed_multiply(fixed_t a, fixed_t b) {
// Promote to 32‑bit to avoid overflow, then shift back
return (fixed_t)(((int32_t)a * (int32_t)b) >> FIXED_SHIFT);
}5. Endianness Conversion
Swaps the two bytes of a 16‑bit value, converting between big‑endian and little‑endian representations. For larger widths, similar byte‑wise shifts can be applied.
uint16_t swap_bytes(uint16_t value) {
return (value >> 8) | (value << 8);
}6. Bit Masks
A macro that generates a mask with a single bit set. It simplifies creating masks for register fields or flag bits.
#define BIT_MASK(bit) (1U << (bit))7. Timer Counting (AVR)
Typical pattern for configuring an AVR hardware timer and reading its current count register ( TCNT1). The example omits specific prescaler settings, which must be chosen according to the desired time base.
#include <avr/io.h>
void setup_timer(void) {
// Example: configure Timer/Counter1 in CTC mode, prescaler = 64
TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10);
OCR1A = 0xFFFF; // top value (optional)
}
uint16_t read_timer(void) {
return TCNT1; // current 16‑bit count
}8. Binary Search
Classic O(log n) search on a sorted integer array. Returns the index of the target or -1 if not found.
int binary_search(const int arr[], int size, int target) {
int left = 0, right = size - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // not found
}9. Simple Bitset
A lightweight bitset that stores up to 32 flags in a single uint32_t. Functions allow setting a bit and querying its value.
#include <stdint.h>
typedef struct {
uint32_t bits; // each bit represents a flag
} Bitset;
void set_bit(Bitset *bs, int bit) {
bs->bits |= (1U << bit);
}
int get_bit(const Bitset *bs, int bit) {
return (bs->bits >> bit) & 1U;
}These nine snippets illustrate frequently used low‑level utilities for embedded C development, helping to reduce code size, improve execution speed, and increase readability.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
