Load Cell
Load cells measure force/weight to detect object presence, evaluate structural loading, or monitor mechanical stress. The system supports a dual load cell configuration, each read through its own HX711 24-bit ADC using GPIO bit-banging. This is the most complete sensor driver on the board: it talks to real hardware with no compile gate.
Hardware Specifications
Parameter | Value |
|---|---|
Sensor Count | 2 (independent) |
ADC | HX711 24-bit, one per load cell |
Interface | GPIO bit-bang (DOUT input, SCK output) |
Gain | Channel A, gain 128 (LOAD_CELL_GAIN_PULSES = 1) |
Measurement | Force (Newtons) / Mass (grams) after calibration; raw counts always available |
Ready timeout | 200 ms (LOAD_CELL_READY_TIMEOUT_MS) |
Supply | HX711 VCC 2.7 to 5.5 V, GND common with the STM32 |
Wiring and Pin Map (from firmware.ioc)
Unit | HX711 DOUT (data, low = ready) | HX711 SCK (clock) |
|---|---|---|
0 | PA5 (WEIGHT_INPUT_1) | PC7 (WEIGHT_CLOCK_1) |
1 | PA6 (WEIGHT_INPUT_2) | PB5 (WEIGHT_CLOCK_2) |
Data Structure
typedef struct {
int32_t raw_counts; /* 24-bit two's complement reading */
float force_newtons;
float mass_grams;
float scale_newtons_per_count; /* default 1.0 (passthrough) */
int32_t tare_offset_counts;
bool is_calibrated; /* true once load_cell_set_scale() called */
bool read_ok; /* true if the last poll succeeded */
/* HX711 hardware binding (set by load_cell_sensor_init_hw) */
GPIO_TypeDef *dout_port;
uint16_t dout_pin;
GPIO_TypeDef *sck_port;
uint16_t sck_pin;
uint8_t gain_pulses;
} load_cell_data_t;
Initialization
Initialize Load Cells (as in main.c)
load_cell_data_t load_cell_data[2];
/* Unit 0: DOUT = PA5, SCK = PC7. Unit 1: DOUT = PA6, SCK = PB5. */
load_cell_sensor_init_hw(&load_cell_data[0], GPIOA, GPIO_PIN_5, GPIOC, GPIO_PIN_7);
load_cell_sensor_init_hw(&load_cell_data[1], GPIOA, GPIO_PIN_6, GPIOB, GPIO_PIN_5);
/* init_hw powers up the HX711 and auto-tares */
/* Alternative: load_cell_sensor_init(&data) zero-initialises WITHOUT a
* hardware binding; poll() then returns RESULT_ERR_UNIMPLEMENTED */
Poll Load Cell Sensor
result_t lc_result = poll_load_cell_sensor(&load_cell_data[i]);
Data Access Functions
float force, scale;
float mass;
int32_t counts, tare;
bool valid;
load_cell_get_force_newtons(&cell, &force);
load_cell_get_mass_grams(&cell, &mass);
load_cell_get_raw_counts(&cell, &counts);
load_cell_get_calibration(&cell, &scale, &tare);
load_cell_sensor_is_valid(&cell, &valid);
Calibration Procedure
Two-Step Calibration
Step 1: Tare (Zero Load)
/* With nothing on the cell: average N raw reads and store as zero offset.
* init_hw already does this automatically at startup. */
load_cell_tare(&cell, 10);
Step 2: Span (Known Weight)
/* Place a known mass, read raw counts, then:
* scale = known_force_newtons / (raw_counts - tare_offset_counts) */
load_cell_set_scale(&cell, newtons_per_count); /* sets is_calibrated = true */
Measurement Formulas
force_newtons = (raw_counts - tare_offset_counts) × scale_newtons_per_count
mass_grams = force_newtons / 9.81 × 1000
Note: before calibration the default scale is 1.0 (passthrough): raw_counts is trustworthy but force_newtons and mass_grams are not physical units yet.
Protobuf Message Format
message SensorBoardLoadCellInfo {
uint32 sensor_index; /* 0 or 1 */
float force_newtons;
float mass_grams;
int32 raw_counts;
float scale_newtons_per_count;
int32 tare_offset_counts;
bool is_calibrated;
SensorState state;
LoadCellErrorCode error_code; /* NO_ERROR, COMMUNICATION_FAILURE, INVALID_DATA */
}
Unit Conversions
From | To | Factor |
|---|---|---|
Newtons | kilograms-force (kgf) | ÷ 9.81 |
Newtons | pounds-force (lbf) | ÷ 4.448 |
grams | kilograms | ÷ 1000 |
Integration Notes
- Two independent units polled every main loop iteration; each is transmitted in its own envelope with its sensor_index
- Each sensor maintains separate calibration (tare + scale) and independent error reporting
- HX711 gain/channel is selected by extra SCK pulses after the 24 data bits (1 = channel A gain 128, 2 = channel B gain 32, 3 = channel A gain 64)
- The blocking wait for data-ready can take up to 200 ms per cell per poll; keep this in mind when reducing the loop interval
- A failed read maps to state ERROR with LOAD_CELL_COMMUNICATION_FAILURE; a read with implausible data maps to LOAD_CELL_INVALID_DATA