#include <stdio.h>
#include <stdint.h>
#include <stdio.h>
#include <stdint.h>
#define MODE_START_POS 0U
#define SPEED_START_POS 3U
#define STATUS_START_POS 10U
#define MODE_END_POS 2U
#define SPEED_END_POS 7U
#define STATUS_END_POS 15U
#define POS_MASK(val) ((1 << (val + 1)) - 1)
// Define macros here
#define SET_MODE(val) (uint16_t)((val & POS_MASK(MODE_END_POS)) << MODE_START_POS)
#define SET_SPEED(val) (uint16_t)((val & POS_MASK(SPEED_END_POS)) << SPEED_START_POS)
#define SET_STATUS(val) (uint16_t)((val & POS_MASK(STATUS_END_POS)) << STATUS_START_POS)
uint16_t pack_register(uint8_t mode, uint8_t speed, uint8_t status)
{
uint16_t reg_val = 0;
reg_val |= SET_MODE(mode);
reg_val |= SET_SPEED(speed);
reg_val |= SET_STATUS(status);
return reg_val;
}
int main()
{
uint8_t mode, speed, status;
scanf("%hhu %hhu %hhu", &mode, &speed, &status);
uint16_t reg = pack_register(mode, speed, status);
printf("%u", reg);
return 0;
}This C program demonstrates Bit Packing, a common technique used in firmware development and communication protocols (like CAN bus) to store multiple distinct values within a single 16-bit register. This is highly memory-efficient for hardware-level programming.
The code packs three separate values into one uint16_t (2-byte) variable. The layout is defined by the start and end positions:
Field | Bit Range | Width | Purpose |
|---|---|---|---|
Mode | 0 to 2 | 3 bits | Low-level configuration state. |
Speed | 3 to 7 | 5 bits | Current speed or velocity value. |
Empty | 8 to 9 | 2 bits | Reserved / Unused (will be 0). |
Status | 10 to 15 | 6 bits | High-level system status flags. |
A. The Masking Macro (POS_MASK)
To prevent one value from "bleeding" into the next field, the code uses a mask.
Mask = (1 << (end_pos + 1)) - 1
This formula creates a string of binary 1s from bit 0 up to the specified end position. For example, a mask for MODE_END_POS (2) results in binary 111 (decimal 7), ensuring only the first 3 bits of the input are used.
B. Shifting and Packing
The program follows a three-step process for each data field:
Masking: The input value is "cleaned" using the & (AND) operator so that only the allowed bits remain.
Shifting: The cleaned value is moved to its correct starting bit position using the << (Left Shift) operator.
Merging: The shifted value is combined into the final register using the | (OR) operator. This "stamps" the bits into the register without overwriting the fields already placed there.
Memory Efficiency: Instead of using three separate bytes (24 bits) to store these variables, they are compressed into a single 16-bit word.
Hardware Compatibility: This structure mimics how hardware registers are physically mapped in microcontrollers (like those used in BMS or motor controllers).
Speed: Bitwise operations are performed directly by the CPU in a single clock cycle, making this much faster than higher-level data structures.
The use of uint16_t and uint8_t from <stdint.h> ensures that the code behaves consistently across different computer architectures, which is critical for DEV/FW (Firmware) environments where bit-exactness is mandatory.
Input
3 10 12
Expected Output
12371