chrismc6201 / wireless reciever

Loaded

0

0

Files

Schematic

PCB

Log InSign Up

Wireless Controller Receiver Design

flux

3:16

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
332 days

​
Sign up to send

To get started, talk to Flux or create a New Document.

Reviews

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

    Welcome 👋

    Flux helps you build PCBs faster with an AI teammate!

    Create your account to collaborate, stay updated, fork your own version, and get instant answers from our AI agent.

        Introducing AI Placement!
        AI Placement Demo

        AI-Powered Component Placement

        Let AI place your components intelligently. AI Placement analyzes your schematic and positions components for optimal routing, signal integrity, and board density.

        From Schematic to Layout in Seconds

        Skip the tedious manual placement. AI Placement generates a starting layout you can refine, saving hours of repetitive work on every new design.

        Works With Auto-Layout

        Pair AI Placement with Auto-Layout for a complete AI-driven PCB design flow — from component placement through trace routing, all with one click.

        Try it