Skip to content

Instantly share code, notes, and snippets.

@BananaHemic
Last active June 19, 2024 23:18
Show Gist options
  • Save BananaHemic/db6e20a3f655d4381366ef462f628425 to your computer and use it in GitHub Desktop.
Save BananaHemic/db6e20a3f655d4381366ef462f628425 to your computer and use it in GitHub Desktop.
NRF52 HX711
// For more information, please refer to https://devzone.nordicsemi.com/f/nordic-q-a/34112/best-approach-to-interface-hx711-with-nordic-nrf52832
// Feel free to comment any questions or suggestions for improvements
//TODO you will have to define the following:
// HX711_TIMER (the index of the timer, 2 for me)
// HX711_TIMER_INTERRUPT (the IRG for the timer, TIMER2_IRQn for me)
// Loadcell_Dout_Pin (the pin number for the HX711 data line)
// Loadcell_Sck_Pin (the pin number for the HX711 clock line)
#include "HX711.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_timer.h"
#include "nrf_gpio.h"
#include "nrf_gpiote.h"
#include "nrfx_ppi.h"
const HX711_GAIN gain = GAIN_A_128;
static const nrf_drv_timer_t hx711Timer = NRF_DRV_TIMER_INSTANCE(HX711_TIMER);
// HX711 ADC resolution in bits
#define ADC_RES 24
#define HX711_CONTINUOUS 1
#define NUM_HX711_BUFFERED 2
uint32_t hx711_results[NUM_HX711_BUFFERED];
uint16_t sampleCount = 0; // The index we're reading from
uint8_t is_loadcell_on = 0;
uint8_t step = 0;
nrf_ppi_channel_t ppi_channel_0;
nrf_ppi_channel_t ppi_channel_1;
static void dout_high2low_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
if (unlikely(!is_loadcell_on))
return;
// DOUT going low means that the hx711 is ready to send us a result
// Turn off listening for DOUT changes (for now)
nrf_drv_gpiote_in_event_disable(Loadcell_Dout_Pin);
// clear working datum
hx711_results[(sampleCount + 1) % NUM_HX711_BUFFERED] = 0;
step = 0;
// Start the timer that will pwm the clock and read out
nrf_drv_timer_resume(&hx711Timer);
}
void hx711_timer_callback(nrf_timer_event_t event_type, void* ctx) {
if (event_type != NRF_TIMER_EVENT_COMPARE1)
return;
if (step < ADC_RES) {
// We read the current datum
uint32_t datum = nrf_gpio_pin_read(Loadcell_Dout_Pin);
// Store datum
uint32_t* res = &hx711_results[(sampleCount + 1) % NUM_HX711_BUFFERED];
res[0] |= datum << (ADC_RES - 1 - step);
} else if (step < gain) {
// Continue ticking
// We do have data ready though
sampleCount++;
} else if (step == gain) {
// hit the end, stop ticking for now
if (is_loadcell_on)
nrf_drv_gpiote_in_event_enable(Loadcell_Dout_Pin, true);
return;
}
step++;
nrf_drv_timer_resume(&hx711Timer);
}
void hx711_init(void) {
ret_code_t err_code;
is_loadcell_on = 0; // Leave off for now
if (!nrfx_gpiote_is_init()) {
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrf_gpio_pin_set(Loadcell_Sck_Pin);
nrf_gpio_cfg(Loadcell_Sck_Pin,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_S0S1,
NRF_GPIO_PIN_NOSENSE);
// Configure DOUT for reading
nrf_gpio_cfg_input(Loadcell_Dout_Pin, NRF_GPIO_PIN_NOPULL);
// Control the clock line with a PPI event
const bool startClockHigh = true;
nrfx_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(startClockHigh);
err_code = nrfx_gpiote_out_init(Loadcell_Sck_Pin, &config);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_out_task_enable(Loadcell_Sck_Pin);
uint32_t toggle_clock_task_addr = nrfx_gpiote_out_task_addr_get(Loadcell_Sck_Pin);
// Set interrupt to low priority, this method is robust to variable latency
NVIC_SetPriority(HX711_TIMER_INTERRUPT, 7);
// Configure the main timer
nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
timer_cfg.frequency = NRF_TIMER_FREQ_1MHz;
timer_cfg.mode = NRF_TIMER_MODE_TIMER; // Mode is timer, not counter
timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_8; // TODO I think 08 would be fine
err_code = nrfx_timer_init(&hx711Timer, &timer_cfg, hx711_timer_callback);
APP_ERROR_CHECK(err_code);
nrf_drv_timer_pause(&hx711Timer);
// Channel 0 turns the clock high
nrfx_timer_compare(&hx711Timer, NRF_TIMER_CC_CHANNEL0, 1, true);
// Channel 1 turns the clock low and clears/stops the timer + calls the interrupt
nrfx_timer_extended_compare(&hx711Timer,
NRF_TIMER_CC_CHANNEL1,
2,
NRF_TIMER_SHORT_COMPARE1_STOP_MASK | NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK,
true);
// Turn clock high on tick 1
err_code = nrfx_ppi_channel_alloc(&ppi_channel_0);
APP_ERROR_CHECK(err_code);
uint32_t compare_evt_addr = nrfx_timer_event_address_get(&hx711Timer, NRF_TIMER_EVENT_COMPARE0);
err_code = nrfx_ppi_channel_assign(ppi_channel_0, compare_evt_addr, toggle_clock_task_addr);
APP_ERROR_CHECK(err_code);
err_code = nrfx_ppi_channel_enable(ppi_channel_0);
APP_ERROR_CHECK(err_code);
// Turn clock low on tick 2
err_code = nrfx_ppi_channel_alloc(&ppi_channel_1);
APP_ERROR_CHECK(err_code);
compare_evt_addr = nrfx_timer_event_address_get(&hx711Timer, NRF_TIMER_EVENT_COMPARE1);
err_code = nrfx_ppi_channel_assign(ppi_channel_1, compare_evt_addr, toggle_clock_task_addr);
APP_ERROR_CHECK(err_code);
err_code = nrfx_ppi_channel_enable(ppi_channel_1);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_config_t gpiote_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(false);
err_code |= nrf_drv_gpiote_in_init(Loadcell_Dout_Pin, &gpiote_config, dout_high2low_handler);
nrf_drv_gpiote_in_event_disable(Loadcell_Dout_Pin);
APP_ERROR_CHECK(err_code);
// printf("hx711 init\n");
}
void hx711_power_on(void) {
if (is_loadcell_on)
return;
is_loadcell_on = true;
// Turn the hx711 on
nrfx_gpiote_out_task_trigger(Loadcell_Sck_Pin);
// If DOUT is low, we're already ready to read
if (nrf_gpio_pin_read(Loadcell_Dout_Pin) == 0) {
// printf("hx711 starting on\n");
hx711_results[(sampleCount + 1) % NUM_HX711_BUFFERED] = 0;
step = 0;
nrf_drv_timer_resume(&hx711Timer);
} else {
// printf("hx711 wait for DOUT\n");
nrfx_gpiote_in_event_enable(Loadcell_Dout_Pin, true);
}
}
void hx711_power_off(void) {
if (!is_loadcell_on)
return;
is_loadcell_on = false;
nrf_drv_gpiote_in_event_disable(Loadcell_Dout_Pin);
// Turn hx711 off
nrfx_gpiote_out_task_trigger(Loadcell_Sck_Pin);
}
static int32_t convert_sample(uint32_t sample) {
return (int32_t)(sample << 8) >> 8;
}
bool hx711_try_read_result(int32_t* result, uint16_t* lastSampleNum) {
if (*lastSampleNum == sampleCount)
return false;
*lastSampleNum = sampleCount;
// We read from the opposite buffer than what's currently being written to
// this is to avoid race condition where we read while it's being written
*result = convert_sample(hx711_results[*lastSampleNum % NUM_HX711_BUFFERED]);
return true;
}
#ifndef HX711_h
#define HX711_h
#include "stdbool.h"
#include "stdint.h"
// From HX711 datasheet
typedef enum {
GAIN_A_128 = 25,
GAIN_A_64 = 27,
GAIN_B_32 = 26,
} HX711_GAIN;
void hx711_init(void);
void hx711_power_on(void);
void hx711_power_off(void);
bool hx711_try_read_result(int32_t* result, uint16_t* lastSampleNum);
#endif /* HX711_h */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment