diff --git a/event_queue.cpp b/event_queue.cpp
new file mode 100644
index 0000000000..6dfca9306c
--- /dev/null
+++ b/event_queue.cpp
@@ -0,0 +1,89 @@
+/**
+ * @file event_queue.cpp
+ * This is a data structure which keeps track of all pending events
+ * Implemented as a linked list, which is fine since the number of
+ * pending events is pretty low
+ * todo: MAYBE migrate to a better data structure, but that's low priority
+ *
+ * this data structure is NOT thread safe
+ *
+ * @date Apr 17, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "event_queue.h"
+#include "efitime.h"
+#include "utlist.h"
+
+EventQueue::EventQueue() {
+ head = NULL;
+}
+
+void EventQueue::insertTask(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param) {
+ if (callback == NULL)
+ firmwareError("NULL callback");
+ uint64_t time = nowUs + delayUs;
+
+ scheduling->momentUs = time;
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER
+ scheduling->callback = callback;
+ scheduling->param = param;
+#endif
+
+ scheduling_s * elt;
+ LL_FOREACH(head, elt)
+ {
+ if (elt == scheduling) {
+ firmwareError("re-adding element");
+ return;
+ }
+ }
+
+ LL_PREPEND(head, scheduling);
+}
+
+void EventQueue::insertTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) {
+ insertTask(scheduling, getTimeNowUs(), delayUs, callback, param);
+}
+
+/**
+ * Get the timestamp of the soonest pending action
+ */
+uint64_t EventQueue::getNextEventTime(uint64_t nowUs) {
+ scheduling_s * elt;
+ // this is a large value which is expected to be larger than any real time
+ uint64_t result = EMPTY_QUEUE;
+
+ LL_FOREACH(head, elt)
+ {
+ if (elt->momentUs <= nowUs) {
+ // todo: I am not so sure about this branch
+ continue;
+ }
+ if (elt->momentUs < result)
+ result = elt->momentUs;
+ }
+ return result;
+}
+
+/**
+ * Invoke all pending actions prior to specified timestamp
+ */
+void EventQueue::executeAll(uint64_t now) {
+ scheduling_s * elt, *tmp;
+
+// here we need safe iteration because we are removing elements
+ LL_FOREACH_SAFE(head, elt, tmp)
+ {
+ if (elt->momentUs <= now) {
+ LL_DELETE(head, elt);
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER
+ elt->callback(elt->param);
+#endif /* EFI_SIGNAL_EXECUTOR_ONE_TIMER */
+ }
+ }
+}
+
+void EventQueue::clear(void) {
+ head = NULL;
+}
diff --git a/event_queue.h b/event_queue.h
new file mode 100644
index 0000000000..3f307c0e31
--- /dev/null
+++ b/event_queue.h
@@ -0,0 +1,30 @@
+/**
+ * @file event_queue.h
+ *
+ * @date Apr 17, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "signal_executor.h"
+
+#ifndef EVENT_SCHEDULER_H_
+#define EVENT_SCHEDULER_H_
+
+#define EMPTY_QUEUE 0x0FFFFFFFFFFFFFFFLL
+
+class EventQueue {
+public:
+ EventQueue();
+
+ void insertTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param);
+ void insertTask(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param);
+
+ void executeAll(uint64_t now);
+
+ uint64_t getNextEventTime(uint64_t nowUs);
+ void clear(void);
+private:
+ scheduling_s *head;
+};
+
+#endif /* EVENT_SCHEDULER_H_ */
diff --git a/firmware/hw_layer/adc_inputs.c b/firmware/hw_layer/adc_inputs.c
index 4d861a9c7f..d512bffb84 100644
--- a/firmware/hw_layer/adc_inputs.c
+++ b/firmware/hw_layer/adc_inputs.c
@@ -72,7 +72,7 @@ static void adc_callback_slow(ADCDriver *adcp, adcsample_t *buffer, size_t n) {
adcCallbackCounter_slow++;
- newState.time = chTimeNow();
+// newState.time = chimeNow();
for (int i = 0; i < EFI_ADC_SLOW_CHANNELS_COUNT; i++) {
int value = getAvgAdcValue(i, slowAdcState.samples, ADC_GRP1_BUF_DEPTH_SLOW, EFI_ADC_SLOW_CHANNELS_COUNT);
newState.adc_data[i] = value;
diff --git a/firmware/hw_layer/adc_inputs.h b/firmware/hw_layer/adc_inputs.h
index 3c2c2c27d1..20dcf311a3 100644
--- a/firmware/hw_layer/adc_inputs.h
+++ b/firmware/hw_layer/adc_inputs.h
@@ -37,7 +37,7 @@ int getInternalAdcValue(int index);
// this structure contains one multi-channel ADC state snapshot
typedef struct {
volatile adcsample_t adc_data[ADC_MAX_SLOW_CHANNELS_COUNT];
- time_t time;
+// time_t time;
} adc_state;
typedef struct {
diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk
index 8153347a00..eb058f719f 100644
--- a/firmware/hw_layer/hw_layer.mk
+++ b/firmware/hw_layer/hw_layer.mk
@@ -8,6 +8,7 @@ HW_LAYERESRC = $(PROJECT_DIR)/hw_layer/hardware.c \
$(PROJECT_DIR)/hw_layer/lcd/lcd_HD44780.c \
$(PROJECT_DIR)/hw_layer/can_hw.c \
$(PROJECT_DIR)/hw_layer/HIP9011.c \
+ $(PROJECT_DIR)/hw_layer/microsecond_timer.c \
$(PROJECT_DIR)/hw_layer/serial_over_usb/usbcfg.c \
$(PROJECT_DIR)/hw_layer/serial_over_usb/usbconsole.c \
$(PROJECT_DIR)/hw_layer/flash.c \
diff --git a/firmware/hw_layer/microsecond_timer.c b/firmware/hw_layer/microsecond_timer.c
new file mode 100644
index 0000000000..068baac96d
--- /dev/null
+++ b/firmware/hw_layer/microsecond_timer.c
@@ -0,0 +1,60 @@
+/**
+ * @file microsecond_timer.c
+ *
+ * Here we have a 1MHz timer dedicated to event scheduling. We are using one of the 32-bit timers here,
+ * so this timer can schedule events up to 4B/100M ~ 4000 seconds ~ 1 hour from current time.
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "signal_executor.h"
+#include "microsecond_timer.h"
+
+// https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https%3a%2f%2fmy.st.com%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex_mx_stm32%2fInterrupt%20on%20CEN%20bit%20setting%20in%20TIM7&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B¤tviews=474
+
+#if EFI_PROD_CODE
+
+static TIM_TypeDef *TIM = TIM5;
+
+schfunc_t globalTimerCallback;
+
+/**
+ * sets the alarm to the specified number of microseconds from now
+ */
+void setHardwareUsTimer(int timeUs) {
+ TIM->ARR = timeUs - 1;
+ TIM->EGR |= TIM_EGR_UG; // generate an update event to reload timer's counter value
+ TIM->CR1 |= TIM_CR1_CEN; // restart timer
+}
+
+static void callback(void) {
+ if (globalTimerCallback == NULL) {
+ firmwareError("NULL globalTimerCallback");
+ return;
+ }
+ globalTimerCallback(NULL);
+}
+
+// if you decide to move this to .cpp do not forget to make that a C method
+CH_IRQ_HANDLER(STM32_TIM5_HANDLER) {
+ CH_IRQ_PROLOGUE();
+ if (((TIM->SR & 0x0001) != 0) && ((TIM->DIER & 0x0001) != 0)) {
+ callback();
+ }
+ TIM->SR = (int) ~STM32_TIM_SR_UIF; // Interrupt has been handled
+ CH_IRQ_EPILOGUE();
+}
+
+void initMicrosecondTimer(void) {
+ RCC ->APB1ENR |= RCC_APB1ENR_TIM5EN; // Enable TIM5 clock
+ nvicEnableVector(TIM5_IRQn, CORTEX_PRIORITY_MASK(12));
+ TIM->DIER |= TIM_DIER_UIE; // Enable interrupt on update event
+ TIM->CR1 |= TIM_CR1_OPM; // one pulse mode: count down ARR and stop
+ TIM->CR1 &= ~TIM_CR1_ARPE; /* ARR register is NOT buffered, allows to update timer's period on-fly. */
+
+ TIM->PSC = 84 - 1; // 168MHz / 2 / 84 = 1MHz, each tick is a microsecond
+}
+
+#endif /* EFI_PROD_CODE */
diff --git a/firmware/hw_layer/microsecond_timer.h b/firmware/hw_layer/microsecond_timer.h
new file mode 100644
index 0000000000..3f2893583f
--- /dev/null
+++ b/firmware/hw_layer/microsecond_timer.h
@@ -0,0 +1,23 @@
+/**
+ * @file microsecond_timer.h
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef SIGNAL_TEMP_H_
+#define SIGNAL_TEMP_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initMicrosecondTimer(void);
+void setHardwareUsTimer(int timeUs);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SIGNAL_TEMP_H_ */
diff --git a/firmware/hw_layer/neo6m.c b/firmware/hw_layer/neo6m.c
index d8b42e23cb..9f7e3f516b 100644
--- a/firmware/hw_layer/neo6m.c
+++ b/firmware/hw_layer/neo6m.c
@@ -47,7 +47,7 @@ static void printGpsInfo(void) {
scheduleMsg(&logging, "m=%d,e=%d: vehicle speed = %f\r\n", gpsMesagesCount, uartErrors, getCurrentSpeed());
- float sec = ((float) chTimeNow() / TICKS_IN_MS) / 1000;
+ float sec = currentTimeMillis() / 1000.0;
scheduleMsg(&logging, "communication speed: %f", gpsMesagesCount / sec);
print("GPS latitude = %f\r\n", GPSdata.latitude);
diff --git a/firmware/hw_layer/pwm_generator.c b/firmware/hw_layer/pwm_generator.c
index a2c9384d24..cf8542158a 100644
--- a/firmware/hw_layer/pwm_generator.c
+++ b/firmware/hw_layer/pwm_generator.c
@@ -55,7 +55,7 @@ void startSimplePwm(PwmConfig *state, char *msg, brain_pin_e brainPin, io_pin_e
outputPinRegister(msg, state->outputPins[0], port, pin);
- state->period = frequency2period(frequency);
+ state->periodMs = frequency2period(frequency);
weComplexInit(msg, state, 2, switchTimes, 1, pinStates, NULL, applyPinState);
}
diff --git a/signal_executor.c b/signal_executor.c
new file mode 100644
index 0000000000..0fe0845b04
--- /dev/null
+++ b/signal_executor.c
@@ -0,0 +1,163 @@
+/**
+ * @file signal_executor.c
+ *
+ * todo: we should split this file into two:
+ * one for pure scheduling and another one for signal output which would
+ * use the scheduling
+ *
+ * @date Dec 4, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "signal_executor.h"
+
+#if EFI_WAVE_CHART
+#include "rpm_calculator.h"
+#endif
+
+#if EFI_WAVE_ANALYZER
+
+/**
+ * Signal executors feed digital events right into WaveChart used by Sniffer tab of Dev Console
+ */
+#include "wave_analyzer.h"
+
+#endif /* EFI_WAVE_ANALYZER */
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ static Logging logger;
+#endif
+
+void initSignalExecutor(void) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initLogging(&logger, "s exec");
+#endif
+ initSignalExecutorImpl();
+}
+
+void initOutputSignalBase(OutputSignal *signal) {
+ signal->status = IDLE;
+// signal->last_scheduling_time = 0;
+ signal->initialized = TRUE;
+}
+
+static void turnHigh(OutputSignal *signal) {
+#if EFI_DEFAILED_LOGGING
+// signal->hi_time = hTimeNow();
+#endif /* EFI_DEFAILED_LOGGING */
+ io_pin_e pin = signal->io_pin;
+ // turn the output level ACTIVE
+ // todo: this XOR should go inside the setOutputPinValue method
+ setOutputPinValue(pin, TRUE);
+ // sleep for the needed duration
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ if(
+ pin == SPARKOUT_1_OUTPUT ||
+ pin == SPARKOUT_3_OUTPUT) {
+// time_t now = hTimeNow();
+// float an = getCrankshaftAngle(now);
+// scheduleMsg(&logger, "spark up%d %d", pin, now);
+// scheduleMsg(&logger, "spark angle %d %f", (int)an, an);
+ }
+#endif
+
+#if EFI_WAVE_CHART
+ addWaveChartEvent(signal->name, "up", "");
+#endif /* EFI_WAVE_ANALYZER */
+}
+
+static void turnLow(OutputSignal *signal) {
+ // turn off the output
+ // todo: this XOR should go inside the setOutputPinValue method
+ setOutputPinValue(signal->io_pin, FALSE);
+
+#if EFI_DEFAILED_LOGGING
+ systime_t after = hTimeNow();
+ debugInt(&signal->logging, "a_time", after - signal->hi_time);
+ scheduleLogging(&signal->logging);
+#endif /* EFI_DEFAILED_LOGGING */
+
+#if EFI_WAVE_CHART
+ addWaveChartEvent(signal->name, "down", "");
+#endif /* EFI_WAVE_ANALYZER */
+}
+
+/**
+ *
+ * @param delay the number of ticks before the output signal
+ * immediate output if delay is zero
+ * @param dwell the number of ticks of output duration
+ *
+ */
+
+int getRevolutionCounter(void);
+
+void scheduleOutput(OutputSignal *signal, float delayMs, float durationMs) {
+ if (durationMs < 0) {
+ firmwareError("duration cannot be negative: %d", durationMs);
+ return;
+ }
+
+ scheduleOutputBase(signal, delayMs, durationMs);
+
+ int index = getRevolutionCounter() % 2;
+ scheduling_s * sUp = &signal->signalTimerUp[index];
+ scheduling_s * sDown = &signal->signalTimerDown[index];
+
+ scheduleTask(sUp, MS2US(delayMs), (schfunc_t) &turnHigh, (void *) signal);
+ scheduleTask(sDown, MS2US(delayMs + durationMs), (schfunc_t) &turnLow, (void*)signal);
+
+// signal->last_scheduling_time = now;
+}
+
+void scheduleOutputBase(OutputSignal *signal, float delayMs, float durationMs) {
+ /**
+ * it's better to check for the exact 'TRUE' value since otherwise
+ * we would accept any memory garbage
+ */
+ chDbgCheck(signal->initialized == TRUE, "Signal not initialized");
+// signal->offset = offset;
+// signal->duration = duration;
+}
+
+
+char *getPinName(io_pin_e io_pin) {
+ switch (io_pin) {
+ case SPARKOUT_1_OUTPUT:
+ return "Spark 1";
+ case SPARKOUT_2_OUTPUT:
+ return "Spark 2";
+ case SPARKOUT_3_OUTPUT:
+ return "Spark 3";
+ case SPARKOUT_4_OUTPUT:
+ return "Spark 4";
+
+ case INJECTOR_1_OUTPUT:
+ return "Injector 1";
+ case INJECTOR_2_OUTPUT:
+ return "Injector 2";
+ case INJECTOR_3_OUTPUT:
+ return "Injector 3";
+ case INJECTOR_4_OUTPUT:
+ return "Injector 4";
+ case INJECTOR_5_OUTPUT:
+ return "Injector 5";
+ default:
+ return "No name";
+ }
+}
diff --git a/signal_executor.h b/signal_executor.h
new file mode 100644
index 0000000000..659848da90
--- /dev/null
+++ b/signal_executor.h
@@ -0,0 +1,111 @@
+/**
+ * @file signal_executor.h
+ * @brief Asynchronous output signal header
+ *
+ * @date Feb 10, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SPARKOUT_H_
+#define SPARKOUT_H_
+
+#include "rusefi_enums.h"
+#include "global.h"
+#include "efifeatures.h"
+#include "io_pins.h"
+
+#if EFI_PROD_CODE
+#include "datalogging.h"
+#endif /* EFI_PROD_CODE */
+
+#if EFI_SIGNAL_EXECUTOR_SLEEP
+#include "signal_executor_sleep.h"
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
+
+#if EFI_SIGNAL_EXECUTOR_SINGLE_TIMER
+#include "signal_executor_single_timer.h"
+#endif /* EFI_SIGNAL_EXECUTOR_SINGLE_TIMER */
+
+typedef void (*schfunc_t)(void *);
+
+typedef struct scheduling_struct scheduling_s;
+struct scheduling_struct {
+ //int initialized;
+#if EFI_SIGNAL_EXECUTOR_SLEEP
+ VirtualTimer timer;
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
+#if EFI_SIGNAL_EXECUTOR_SINGLE_TIMER
+ volatile time_t moment;
+#endif /* EFI_SIGNAL_EXECUTOR_SINGLE_TIMER */
+
+ volatile uint64_t momentUs;
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER
+ schfunc_t callback;
+ void *param;
+#endif
+
+ scheduling_s *next;
+};
+
+typedef enum {
+ IDLE = 0, ACTIVE
+} executor_status_t;
+
+/**
+ * @brief Asynchronous output signal data structure
+ */
+typedef struct OutputSignal_struct OutputSignal;
+struct OutputSignal_struct {
+ /**
+ * name of this signal
+ */
+ char *name;
+ io_pin_e io_pin;
+#if 0 // depricated
+ // time in system ticks
+ volatile int offset;
+ // time in system ticks
+ volatile int duration;
+#endif
+ int initialized;
+
+// time_t last_scheduling_time;
+// time_t hi_time;
+
+ /**
+ * We are alternating instances so that events which extend into next revolution are not overriden while
+ * scheduling next revolution events
+ */
+ scheduling_s signalTimerUp[2];
+ scheduling_s signalTimerDown[2];
+
+ executor_status_t status;
+
+#if EFI_SIGNAL_EXECUTOR_HW_TIMER
+ // todo
+#endif
+
+ OutputSignal *next;
+};
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initOutputSignal(OutputSignal *signal, io_pin_e ioPin);
+void scheduleOutput(OutputSignal *signal, float delayMs, float durationMs);
+void initOutputSignalBase(OutputSignal *signal);
+void scheduleOutputBase(OutputSignal *signal, float delayMs, float durationMs);
+
+void initSignalExecutor(void);
+void initSignalExecutorImpl(void);
+void scheduleTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param);
+void scheduleByAngle(scheduling_s *timer, float angle, schfunc_t callback, void *param);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SPARKOUT_H_ */
diff --git a/signal_executor_single_timer_algo.c b/signal_executor_single_timer_algo.c
new file mode 100644
index 0000000000..73893445dd
--- /dev/null
+++ b/signal_executor_single_timer_algo.c
@@ -0,0 +1,74 @@
+/**
+ * @file signal_executor_single_timer_algo.c
+ *
+ * @date Nov 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "signal_executor.h"
+#include "signal_executor_single_timer_algo.h"
+#include "main.h"
+#include "utlist.h"
+#include "io_pins.h"
+
+#if EFI_WAVE_ANALYZER
+#include "wave_analyzer.h"
+#include "wave_chart.h"
+extern WaveChart waveChart;
+#endif
+
+#if EFI_SIGNAL_EXECUTOR_SINGLE_TIMER
+/**
+ * @brief Output list
+ *
+ * List of all active output signals
+ * This is actually the head of the list.
+ * When the list is empty (initial state) the head of the list should be NULL.
+ * This is by design.
+ */
+OutputSignal *st_output_list = NULL;
+
+inline void registerSignal(OutputSignal *signal) {
+ LL_APPEND(st_output_list, signal);
+}
+
+void setOutputPinValue(io_pin_e pin, int value);
+
+/**
+ * @return time of next event within for this signal
+ * @todo Find better name.
+ */
+inline time_t toggleSignalIfNeeded(OutputSignal *out, time_t now) {
+// chDbgCheck(out!=NULL, "out is NULL");
+// chDbgCheck(out->io_pin < IO_PIN_COUNT, "pin assertion");
+ time_t last = out->last_scheduling_time;
+ //estimated = last + out->timing[out->status];
+ time_t estimated = last + GET_DURATION(out);
+ if (now >= estimated) {
+ out->status ^= 1; /* toggle status */
+ //setOutputPinValue(out->io_pin, out->status); /* Toggle output */
+ palWritePad(GPIOE, 5, out->status);
+#if EFI_WAVE_ANALYZER
+// addWaveChartEvent(out->name, out->status ? "up" : "down", "");
+#endif /* EFI_WAVE_ANALYZER */
+
+// out->last_scheduling_time = now; /* store last update */
+ estimated = now + GET_DURATION(out); /* update estimation */
+ }
+ return estimated - now;
+}
+#endif /* EFI_SIGNAL_EXECUTOR_SINGLE_TIMER */
diff --git a/snow_blower.c b/snow_blower.c
new file mode 100644
index 0000000000..c09ccfc50c
--- /dev/null
+++ b/snow_blower.c
@@ -0,0 +1,14 @@
+/**
+ * @file snow_blower.c
+ * @brief Default configuration of a single-cylinder engine
+ *
+ * @date Sep 9, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_ENGINE_SNOW_BLOWER
+
+
+#endif