From 237443fffbc49894f6ced014da75d6856d79d50d Mon Sep 17 00:00:00 2001 From: Andrey B Date: Fri, 25 Apr 2014 22:46:43 -0500 Subject: [PATCH] ? --- event_queue.cpp | 89 ++++++++++++++ event_queue.h | 30 +++++ firmware/hw_layer/adc_inputs.c | 2 +- firmware/hw_layer/adc_inputs.h | 2 +- firmware/hw_layer/hw_layer.mk | 1 + firmware/hw_layer/microsecond_timer.c | 60 ++++++++++ firmware/hw_layer/microsecond_timer.h | 23 ++++ firmware/hw_layer/neo6m.c | 2 +- firmware/hw_layer/pwm_generator.c | 2 +- signal_executor.c | 163 ++++++++++++++++++++++++++ signal_executor.h | 111 ++++++++++++++++++ signal_executor_single_timer_algo.c | 74 ++++++++++++ snow_blower.c | 14 +++ 13 files changed, 569 insertions(+), 4 deletions(-) create mode 100644 event_queue.cpp create mode 100644 event_queue.h create mode 100644 firmware/hw_layer/microsecond_timer.c create mode 100644 firmware/hw_layer/microsecond_timer.h create mode 100644 signal_executor.c create mode 100644 signal_executor.h create mode 100644 signal_executor_single_timer_algo.c create mode 100644 snow_blower.c 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