#include #include #include #include #include #include #include LOG_MODULE_REGISTER(valve, LOG_LEVEL_DBG); // ADC configuration for MULTISENSE (PA0) static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1)); static const struct adc_channel_cfg adc_channel_cfg = { .gain = ADC_GAIN_1, .reference = ADC_REF_INTERNAL, .acquisition_time = ADC_ACQ_TIME_DEFAULT, .channel_id = 1, // ADC1_IN1 (PA0) }; static const struct valve_gpios valve_gpios = { .in0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in0_gpios), .in1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in1_gpios), .rst = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), rst_gpios), .sen = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), sen_gpios), .s0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s0_gpios), .s1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s1_gpios), }; static enum valve_state current_state = VALVE_STATE_CLOSED; static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE; static uint16_t max_opening_time_s = 60; static uint16_t max_closing_time_s = 60; static struct k_work_delayable valve_work; static void valve_work_handler(struct k_work *work) { gpio_pin_set_dt(&valve_gpios.in0, 0); gpio_pin_set_dt(&valve_gpios.in1, 0); gpio_pin_set_dt(&valve_gpios.rst, 0); if (current_movement == VALVE_MOVEMENT_OPENING) { LOG_INF("Valve finished opening"); } else if (current_movement == VALVE_MOVEMENT_CLOSING) { current_state = VALVE_STATE_CLOSED; LOG_INF("Valve finished closing"); } current_movement = VALVE_MOVEMENT_IDLE; } void valve_init(void) { k_work_init_delayable(&valve_work, valve_work_handler); settings_load_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); settings_load_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s)); // Initialize ADC for MULTISENSE if (!device_is_ready(adc_dev)) { LOG_ERR("ADC device not ready"); return; } int ret = adc_channel_setup(adc_dev, &adc_channel_cfg); if (ret < 0) { LOG_ERR("Could not setup ADC channel (%d)", ret); return; } gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE); // IN0 control pin - output, deactivate gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE); // IN1 control pin - output, deactivate gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT_INACTIVE); // Keep VND7050AJ in reset gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE); // Sensor enable pin - output, inactive // S0 and S1 pins are used for selecting the valve state, they are initially inactive // and will be set to active when the valve is opened or closed. gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT_INACTIVE); // S0 select pin - output gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT_INACTIVE); // S1 select pin - output LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s, max_closing_time_s); } void valve_open(void) { if (current_state == VALVE_STATE_CLOSED) { gpio_pin_set_dt(&valve_gpios.rst, 1); gpio_pin_set_dt(&valve_gpios.in1, 0); gpio_pin_set_dt(&valve_gpios.in0, 1); current_state = VALVE_STATE_OPEN; current_movement = VALVE_MOVEMENT_OPENING; k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9)); } } void valve_close(void) { if (current_state == VALVE_STATE_OPEN) { gpio_pin_set_dt(&valve_gpios.rst, 1); gpio_pin_set_dt(&valve_gpios.in0, 0); gpio_pin_set_dt(&valve_gpios.in1, 1); current_movement = VALVE_MOVEMENT_CLOSING; k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9)); } } void valve_stop(void) { k_work_cancel_delayable(&valve_work); current_movement = VALVE_MOVEMENT_IDLE; } enum valve_state valve_get_state(void) { return current_state; } enum valve_movement valve_get_movement(void) { return current_movement; } uint16_t valve_get_motor_current(void) { return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10; } uint16_t valve_get_supply_voltage(void) { LOG_DBG("Starting supply voltage measurement"); // Ensure VND7050AJ is enabled (RST=HIGH) LOG_DBG("Enabling VND7050AJ (RST=1)"); gpio_pin_set_dt(&valve_gpios.rst, 1); // Wait for VND7050AJ to power up and stabilize k_msleep(50); int16_t buf; struct adc_sequence sequence = { .buffer = &buf, .buffer_size = sizeof(buf), .channels = BIT(adc_channel_cfg.channel_id), .resolution = 12, }; // Configure VND7050AJ to output supply voltage on MULTISENSE // According to VND7050AJ datasheet page 20: // S0=1, S1=1: Supply voltage sensing mode LOG_DBG("Setting S0=1, S1=1 for supply voltage sensing"); gpio_pin_set_dt(&valve_gpios.s0, 1); gpio_pin_set_dt(&valve_gpios.s1, 1); // Enable sensing LOG_DBG("Enabling MULTISENSE (SEN=1)"); gpio_pin_set_dt(&valve_gpios.sen, 1); // Wait for voltage to stabilize k_msleep(10); // Read ADC value LOG_DBG("Reading ADC channel %d", adc_channel_cfg.channel_id); int ret = adc_read(adc_dev, &sequence); if (ret < 0) { LOG_ERR("Could not read ADC (%d)", ret); gpio_pin_set_dt(&valve_gpios.sen, 0); return 0; } // Disable sensing to save power LOG_DBG("Disabling MULTISENSE (SEN=0)"); gpio_pin_set_dt(&valve_gpios.sen, 0); // Convert ADC value to millivolts // VDD = 3.3V, ADC resolution = 12-bit (4096 steps) // ADC voltage = (buf / 4096) * 3300 mV int32_t val_mv = ((int32_t)buf * 3300) / 4096; // VND7050AJ MULTISENSE voltage divider: // According to datasheet page 35, MULTISENSE = VCC / 8 (8:1 voltage divider) // So actual supply voltage = MULTISENSE * 8 uint16_t supply_voltage_mv = (uint16_t)(val_mv * 8); LOG_INF("Supply voltage: %u mV (ADC raw: %d, ADC mV: %d)", supply_voltage_mv, buf, (int)val_mv); return supply_voltage_mv; } void valve_set_max_open_time(uint16_t seconds) { max_opening_time_s = seconds; settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); } void valve_set_max_close_time(uint16_t seconds) { max_closing_time_s = seconds; settings_save_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s)); } uint16_t valve_get_max_open_time(void) { return max_opening_time_s; } uint16_t valve_get_max_close_time(void) { return max_closing_time_s; }