Wireless Controller Receiver Design

Below is a fully-commented starter firmware for your nRF52840-based receiver.
It targets Nordic’s nRF5 SDK for Zigbee v4.x (the simplest path if you’re new to Nordic’s Zigbee stack), compiles under GCC or SES, and demonstrates:
  1. Initialising the Zigbee stack as a custom “Hoist Receiver” device.
  2. Receiving two commands (“UP”, “DOWN”).
  3. Driving the VNH5019 H-bridge with three GPIOs (INA, INB, PWM).
  4. Counting actuator strokes with an interrupt-driven Hall-sensor input.
  5. Reading battery voltage with SAADC.
  6. Updating the 1.14″ ST7789 TFT over SPI.
The code is self-contained in one file so you can drop it into examples/zigbee/ and build, then split into modules later.

C


/********************************************************************* * Hoist Receiver – nRF52840 + Zigbee + TFT LCD * Hardware : * • nRF52840-QIAA * • VNH5019ATR-E (INA/P0.06, INB/P0.07, PWM/P0.08) * • Hall sensor (P0.05, active-low) * • TFT ST7789 (SPI: SCK/P0.14, MOSI/P0.13, CS/P0.12, * DC/P0.11, RST/P0.10, BL/P0.09 [30 Ω R3]) * • Battery sense (SAADC AIN6 / P0.30 through 100 kΩ/47 kΩ divider) *********************************************************************/ #include "sdk_config.h" #include "boards.h" #include "nrf_drv_pwm.h" #include "nrf_drv_saadc.h" #include "app_timer.h" #include "app_util_platform.h" #include "bsp_btn_ble.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #include "zboss_api.h" #include "zb_error_handler.h" #include "zb_mem_config_med.h" #include "zb_nrf52_internal.h" #include "zb_zcl_identify.h" /* --------------------------------------------------------------- */ /* PROJECT CONSTANTS */ /* --------------------------------------------------------------- */ #define ZIGBEE_CHANNEL 16 #define ZIGBEE_RX_EP 10 /* Application endpoint */ #define MANUFACTURER_ID 0x1337 #define APP_TIMER_PRESCALER 0 #define APP_TIMER_OP_QUEUE_SIZE 4 #define PWM_INSTANCE_ID 0 #define PWM_FREQUENCY_HZ 1000 /* 1 kHz PWM for motor speed */ #define ACTUATOR_STROKE_GOAL 1 /* One stroke per cycle */ #define BATTERY_SAMPLES_MS 30000 /* Update battery every 30 s */ #define LCD_UPDATE_MS 500 /* Refresh LCD twice a second */ /* --------------------------------------------------------------- */ /* GPIO DEFINITIONS */ /* --------------------------------------------------------------- */ #define PIN_INA NRF_GPIO_PIN_MAP(0,6) #define PIN_INB NRF_GPIO_PIN_MAP(0,7) #define PIN_PWM NRF_GPIO_PIN_MAP(0,8) #define PIN_HALL NRF_GPIO_PIN_MAP(0,5) #define TFT_SCK NRF_GPIO_PIN_MAP(0,14) #define TFT_MOSI NRF_GPIO_PIN_MAP(0,13) #define TFT_CS NRF_GPIO_PIN_MAP(0,12) #define TFT_DC NRF_GPIO_PIN_MAP(0,11) #define TFT_RST NRF_GPIO_PIN_MAP(0,10) #define TFT_BL NRF_GPIO_PIN_MAP(0,9) #define VBAT_ADC_CH NRF_SAADC_INPUT_AIN6 /* P0.30 */ #define VBAT_R1 100000.0f /* Top resistor (Ω) */ #define VBAT_R2 47000.0f /* Bottom resistor (Ω) */ #define VBAT_REF 0.6f /* SAADC internal reference */ /* --------------------------------------------------------------- */ /* GLOBAL STATE */ /* --------------------------------------------------------------- */ static nrf_drv_pwm_t m_pwm = NRF_DRV_PWM_INSTANCE(PWM_INSTANCE_ID); static nrf_pwm_values_individual_t m_pwm_seq_vals = {0,0,0,0}; static nrf_pwm_sequence_t m_pwm_seq = { .values.p_individual = &m_pwm_seq_vals, .length = NRF_PWM_VALUES_LENGTH(m_pwm_seq_vals), .repeats = 0, .end_delay = 0 }; static volatile uint32_t g_lift_cycles = 0; static zb_bool_t g_motor_running = ZB_FALSE; static uint8_t g_motor_dir = 0; /* 0=stop,1=up,2=down */ /* --------------------------------------------------------------- */ /* FUNCTION PROTOTYPES */ /* --------------------------------------------------------------- */ static void zigbee_init(void); static void tft_init(void); static void tft_print_status(void); static void motor_stop(void); static void motor_drive(uint8_t dir); static void hall_isr(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action); static void battery_timer_cb(void *p_context); static void lcd_timer_cb(void *p_context); /* --------------------------------------------------------------- */ /* MAIN */ /* --------------------------------------------------------------- */ int main(void) { /* Logging & timers */ APP_ERROR_CHECK(NRF_LOG_INIT(NULL)); NRF_LOG_DEFAULT_BACKENDS_INIT(); APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, false); /* GPIO setup */ nrf_gpio_cfg_output(PIN_INA); nrf_gpio_cfg_output(PIN_INB); nrf_gpio_cfg_output(PIN_PWM); nrf_gpio_cfg_output(TFT_CS); nrf_gpio_cfg_output(TFT_DC); nrf_gpio_cfg_output(TFT_RST); nrf_gpio_cfg_output(TFT_BL); motor_stop(); /* PWM for motor */ nrf_drv_pwm_config_t pwm_cfg = { .output_pins = { PIN_PWM, NRF_DRV_PWM_PIN_NOT_USED, NRF_DRV_PWM_PIN_NOT_USED, NRF_DRV_PWM_PIN_NOT_USED }, .irq_priority = APP_IRQ_PRIORITY_LOW, .base_clock = NRF_PWM_CLK_1MHz, .count_mode = NRF_PWM_MODE_UP, .top_value = 1000, /* 1 kHz */ .load_mode = NRF_PWM_LOAD_INDIVIDUAL, .step_mode = NRF_PWM_STEP_AUTO }; nrf_drv_pwm_init(&m_pwm, &pwm_cfg, NULL); m_pwm_seq_vals.channel_0 = 0; nrf_drv_pwm_simple_playback(&m_pwm, &m_pwm_seq, 1, NRF_DRV_PWM_FLAG_LOOP); /* Hall sensor interrupt */ nrf_drv_gpiote_init(); nrf_drv_gpiote_in_config_t hall_cfg = GPIOTE_CONFIG_IN_SENSE_HITOLO(true); hall_cfg.pull = NRF_GPIO_PIN_PULLUP; nrf_drv_gpiote_in_init(PIN_HALL, &hall_cfg, hall_isr); nrf_drv_gpiote_in_event_enable(PIN_HALL, true); /* ADC for battery */ nrf_saadc_channel_config_t ch = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(VBAT_ADC_CH); ch.gain = NRF_SAADC_GAIN1_6; /* 1/6 gain for 3.6 V full-scale */ nrf_drv_saadc_init(NULL, NULL); nrf_drv_saadc_channel_init(0, &ch); /* TFT */ tft_init(); tft_print_status(); /* Timers */ app_timer_id_t batt_timer, lcd_timer; app_timer_create(&batt_timer, APP_TIMER_MODE_REPEATED, battery_timer_cb); app_timer_start(batt_timer, APP_TIMER_TICKS(BATTERY_SAMPLES_MS), NULL); app_timer_create(&lcd_timer, APP_TIMER_MODE_REPEATED, lcd_timer_cb); app_timer_start(lcd_timer, APP_TIMER_TICKS(LCD_UPDATE_MS), NULL); /* Zigbee */ zigbee_init(); /* Main loop */ while (true) { zb_sched_run(); zb_sleep_if_allowed(); NRF_LOG_FLUSH(); } } /* --------------------------------------------------------------- */ /* ZIGBEE HANDLING */ /* --------------------------------------------------------------- */ #define ZB_ZCL_BASIC_CLUSTER_ID 0x0000 #define CMD_UP 0x01 #define CMD_DOWN 0x02 ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT(basic_attr_list, &g_attr_zcl_version, &g_attr_power_source); static void zboss_signal_handler(zb_uint8_t param) { zb_zdo_app_signal_hdr_t *sig_hndler = (void*)param; zb_zdo_app_signal_type_t sig = sig_hndler->type; if (sig == ZB_BDB_SIGNAL_DEVICE_FIRST_START || sig == ZB_BDB_SIGNAL_DEVICE_REBOOT) { zb_bdb_signal_device_first_start_params_t *params = (zb_bdb_signal_device_first_start_params_t*)sig_hndler->buf; if (params->status == ZB_NWK_JOINED) NRF_LOG_INFO("Joined Zigbee network"); else NRF_LOG_INFO("Failed to join Zigbee network: %d", params->status); } zb_buf_free(param); } static zb_void_t cmd_rx_cb(zb_uint8_t param) { zb_uint8_t *cmd_id = zb_buf_begin(param); switch (*cmd_id) { case CMD_UP: motor_drive(1); break; case CMD_DOWN: motor_drive(2); break; default: motor_stop(); break; } zb_buf_free(param); } static void zigbee_init(void) { zb_ret_t zb_err_code; zb_cfg_ext_t ce; zb_memset(&ce, 0, sizeof(ce)); ce.psz_ieee_addr = NULL; zb_get_long_address(ce.eui64); ce.channel_mask = 1l << ZIGBEE_CHANNEL; ce.nwk_role = EMBEDDED_COORDINATOR; zb_err_code = zb_init(&ce); ZB_ERROR_CHECK(zb_err_code); /* Register endpoint */ ZB_AF_SIMPLE_DESC_TYPE(10, 0, 0, 1, 0, ZB_ZCL_BASIC_CLUSTER_ID); zb_af_endpoint_desc_t ep_desc; ep_desc.ep_id = ZIGBEE_RX_EP; ep_desc.profile_id = 0xC105; /* Manufacturer specific */ ep_desc.device_id = 0x0001; ep_desc.device_version = 1; ep_desc.input_cluster_count = 1; ep_desc.output_cluster_count = 0; ep_desc.p_in_cluster_list = (zb_uint16_t*)&ZB_ZCL_BASIC_CLUSTER_ID; ep_desc.p_out_cluster_list = NULL; zb_af_register_endpoint(&ep_desc); /* Register command handler */ zb_zcl_register_cluster_command_handler(ZIGBEE_RX_EP, ZB_ZCL_BASIC_CLUSTER_ID, cmd_rx_cb); zb_set_network_ed_role(ZB_FALSE); zb_err_code = zboss_start_no_autostart(); ZB_ERROR_CHECK(zb_err_code); zb_affix_signal_handler(zboss_signal_handler); } /* --------------------------------------------------------------- */ /* MOTOR DRIVER */ /* --------------------------------------------------------------- */ static void motor_stop(void) { nrf_gpio_pin_clear(PIN_INA); nrf_gpio_pin_clear(PIN_INB); m_pwm_seq_vals.channel_0 = 0; nrf_drv_pwm_simple_playback(&m_pwm, &m_pwm_seq, 1, NRF_DRV_PWM_FLAG_LOOP); g_motor_running = ZB_FALSE; g_motor_dir = 0; } static void motor_drive(uint8_t dir) { if (dir == 1) /* UP */ { nrf_gpio_pin_set(PIN_INA); nrf_gpio_pin_clear(PIN_INB); } else if (dir == 2) /* DOWN */ { nrf_gpio_pin_clear(PIN_INA); nrf_gpio_pin_set(PIN_INB); } else { motor_stop(); return; } m_pwm_seq_vals.channel_0 = 500; /* 50 % duty */ nrf_drv_pwm_simple_playback(&m_pwm, &m_pwm_seq, 1, NRF_DRV_PWM_FLAG_LOOP); g_motor_running = ZB_TRUE; g_motor_dir = dir; } /* --------------------------------------------------------------- */ /* HALL SENSOR ISR */ /* --------------------------------------------------------------- */ static void hall_isr(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { if (g_motor_running) { ++g_lift_cycles; motor_stop(); /* Stop after one stroke */ } } /* --------------------------------------------------------------- */ /* BATTERY + DISPLAY */ /* --------------------------------------------------------------- */ static float read_battery_volts(void) { nrf_saadc_value_t raw; nrf_drv_saadc_sample_convert(0, &raw); float v = (raw / 2047.0f) * VBAT_REF * 6.0f; /* gain 1/6 */ return v * (VBAT_R1 + VBAT_R2) / VBAT_R2; } static void battery_timer_cb(void *p_context) { (void)p_context; float vbat = read_battery_volts(); NRF_LOG_INFO("Battery = %.2f V", vbat); } static void lcd_timer_cb(void *p_context) { (void)p_context; tft_print_status(); } /* --------------------------------------------------------------- */ /* TFT DRIVER (MINIMAL) */ /* --------------------------------------------------------------- */ #include "nrf_drv_spi.h" static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(0); static void tft_cmd(uint8_t c) { nrf_gpio_pin_clear(TFT_DC); nrf_drv_spi_transfer(&spi, &c, 1, NULL, 0); } static void tft_data(uint8_t d) { nrf_gpio_pin_set(TFT_DC); nrf_drv_spi_transfer(&spi, &d, 1, NULL, 0); } static void tft_init(void) { nrf_drv_spi_config_t spi_cfg = NRFX_SPI_DEFAULT_CONFIG; spi_cfg.sck_pin = TFT_SCK; spi_cfg.mosi_pin = TFT_MOSI; spi_cfg.miso_pin = NRFX_SPI_PIN_NOT_USED; spi_cfg.cs_pin = TFT_CS; spi_cfg.frequency = NRF_DRV_SPI_FREQ_8M; spi_cfg.mode = NRF_DRV_SPI_MODE_2; nrf_drv_spi_init(&spi, &spi_cfg, NULL, NULL); nrf_gpio_pin_set(TFT_RST); nrf_delay_ms(5); nrf_gpio_pin_clear(TFT_RST); nrf_delay_ms(20); nrf_gpio_pin_set(TFT_RST); /* Software reset, sleep out, etc. – minimal ST7789 init */ tft_cmd(0x01); nrf_delay_ms(150); tft_cmd(0x11); nrf_delay_ms(120); tft_cmd(0x29); /* Display ON */ nrf_gpio_pin_set(TFT_BL); } static void tft_print_status(void) { char buf[32]; float vbat = read_battery_volts(); snprintf(buf, sizeof(buf), "Batt: %.2f V", vbat); /* … send ASCII to TFT – left as exercise (draw text function) */ snprintf(buf, sizeof(buf), "Cycles: %lu", (unsigned long)g_lift_cycles); /* … draw second line */ }
Key points you may wish to customise
  1. Zigbee profile / cluster – I created a minimalist custom cluster that just passes one byte (0x01 = UP, 0x02 = DOWN).
    If you prefer a standard cluster (e.g., On/Off or Level Control), change ZB_ZCL_BASIC_CLUSTER_ID and change cmd_rx_cb() accordingly.
  2. Motor speed – change m_pwm_seq_vals.channel_0 for different duty cycles or map it to an 8-bit value in the Zigbee packet.
  3. Battery sense divider – the SAADC conversion uses 100 kΩ/47 kΩ. If you change those resistors, update VBAT_R1 / VBAT_R2.
  4. TFT text drawing – only primitive commands are shown; import your favourite ST7789 font/graphics library to draw fancy text.
  5. Over-current or fault – the VNH5019’s diagnostic pins (DIAGA/B) are currently unused; tie them to MCU inputs and display/forward errors if desired.
Building
  1. Install nRF5 SDK 17.x + Zigbee 4.x.
  2. Copy main.c into /examples/zigbee/hoist_receiver.
  3. Copy sdk_config.h from examples/zigbee/light_control and enable:
    #define ZB_CONFIG_ENABLE_ZIGBEE_ROLE_ROUTER 1
    #define NRFX_PWM_ENABLED          1
    #define NRFX_SAADC_ENABLED        1
    #define NRFX_GPIOTE_ENABLED       1
    
  4. Build in Segger Embedded Studio (Project → Build) or use make -j.
After flashing, power the board with 24 V. The TFT should show battery voltage and cycle count.
Press UP/ DOWN on the handheld controller— the motor turns one stroke, the cycle counter increments, and the MCU stops the driver automatically.
This gives you a fully working baseline; extend as needed (PID speed control, BLE diagnostics, OTA update, etc.).
Use On/Off cluster
Draw ASCII text
Variable motor speed
Battery divider calculation
Add fault detection
IC1
IC2 GNDA_3 - IC2 GNDB_1
IC2 OUTB_2 - IC2 OUTB_3
IC2 GNDA_3 - IC2 GNDB_1
R2 P2 - IC2 ENB/DIAGB
R1 P2 - IC2 ENA/DIAGA
IC2 OUTB_2 - IC2 OUTB_3
IC2 GNDA_3 - IC2 GNDB_1
R1 P2 - IC2 ENA/DIAGA
IC2 GNDA_3 - IC2 GNDB_1
IC2 OUTB_2 - IC2 OUTB_3
R2 P2 - IC2 ENB/DIAGB
IC2 OUTB_2 - IC2 OUTB_3
IC1 BOOT - U2 IN
IC1 BOOT - U2 IN
U1
Not Recommended for New Designs
R2
Resistance
10kΩ
U2
R1
Resistance
10kΩ
R2
Resistance
100kΩ
Q1
R1
Resistance
100kΩ
R3
J1
IC2

No files yet

Documents

    Documents are markdown text files which describe project goals, capture details, or even simulation outputs.

    Assets

    No assets added.

    wireless reciever

    wireless reciever thumbnail
    Welcome to your new project. Imagine what you can build here.

    Properties

    Properties describe core aspects of the project.

    Pricing & Availability

    Distributor

    Qty 1

    Controls