Last active
August 5, 2024 17:19
-
-
Save BananaHemic/db6e20a3f655d4381366ef462f628425 to your computer and use it in GitHub Desktop.
NRF52 HX711
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 == ADC_RES) { | |
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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