/* * Copyright (C) EdgeTX * * Based on code named * opentx - https://github.com/opentx/opentx * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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. */ #define SIMPGMSPC_USE_QT 0 #if defined(SIMU_AUDIO) #include #endif #include "opentx.h" #include "simulcd.h" #include "hal/adc_driver.h" #include "hal/rotary_encoder.h" #include #include #include #if !defined (_MSC_VER) || defined (__GNUC__) #include #include #endif int g_snapshot_idx = 0; uint8_t simu_start_mode = 0; char * main_thread_error = nullptr; bool simu_shutdown = false; bool simu_running = false; volatile rotenc_t rotencValue = 0; volatile uint32_t rotencDt = 0; rotenc_t rotaryEncoderGetValue() { return rotencValue / ROTARY_ENCODER_GRANULARITY; } rotenc_t rotaryEncoderGetRawValue() { return rotencValue; } // TODO: remove all STM32 defs extern const etx_hal_adc_driver_t simu_adc_driver; void lcdCopy(void * dest, void * src); FATFS g_FATFS_Obj; uint64_t simuTimerMicros(void) { #if SIMPGMSPC_USE_QT static QElapsedTimer ticker; if (!ticker.isValid()) ticker.start(); return ticker.nsecsElapsed() / 1000; #elif defined(_MSC_VER) static double freqScale = 0.0; static LARGE_INTEGER firstTick; LARGE_INTEGER newTick; if (!freqScale) { LARGE_INTEGER frequency; // get ticks per second QueryPerformanceFrequency(&frequency); // 1us resolution freqScale = 1e6 / frequency.QuadPart; // init timer QueryPerformanceCounter(&firstTick); TRACE_SIMPGMSPACE("microsTimer() init: first tick = %llu @ %llu Hz", firstTick.QuadPart, frequency.QuadPart); } // read the timer QueryPerformanceCounter(&newTick); // compute the elapsed time return (newTick.QuadPart - firstTick.QuadPart) * freqScale; #else // GNUC auto now = std::chrono::steady_clock::now(); return (uint64_t) std::chrono::duration_cast(now.time_since_epoch()).count(); #endif } uint16_t getTmr16KHz() { return simuTimerMicros() * 2 / 125; } uint16_t getTmr2MHz() { return simuTimerMicros() * 2; } // return 2ms resolution to match CoOS settings uint64_t CoGetOSTime(void) { return simuTimerMicros() / 2000; } void simuInit() { #if defined(ROTARY_ENCODER_NAVIGATION) rotencValue = 0; #endif // Init ADC driver callback adcInit(&simu_adc_driver); } bool keysStates[MAX_KEYS] = { false }; void simuSetKey(uint8_t key, bool state) { // TRACE("simuSetKey(%d, %d)", key, state); assert(key < DIM(keysStates)); keysStates[key] = state; } bool trimsStates[MAX_TRIMS * 2] = { false }; void simuSetTrim(uint8_t trim, bool state) { // TRACE("simuSetTrim(%d, %d)", trim, state); assert(trim < DIM(trimsStates)); trimsStates[trim] = state; } #if defined(SIMU_BOOTLOADER) int bootloaderMain(); static void* bootloaderThread(void*) { bootloaderMain(); return nullptr; } #endif void simuStart(bool tests, const char * sdPath, const char * settingsPath) { if (simu_running) return; #if !defined(COLORLCD) menuLevel = 0; #endif simu_start_mode = (tests ? 0 : OPENTX_START_NO_SPLASH | OPENTX_START_NO_CALIBRATION | OPENTX_START_NO_CHECKS); simu_shutdown = false; simuFatfsSetPaths(sdPath, settingsPath); /* g_tmr10ms must be non-zero otherwise some SF functions (that use this timer as a marker when it was last executed) will be executed twice on startup. Normal radio does not see this issue because g_tmr10ms is already a big number before the first call to the Special Functions. Not so in the simulator. There is another issue, some other function static variables depend on this value. If simulator is started multiple times in one Companion session, they are set to their initial values only first time the simulator is started. Therefore g_tmr10ms must also be set to non-zero value only the first time, then it must be left alone to continue from the previous simulator session value. See the issue #2446 */ if (g_tmr10ms == 0) { g_tmr10ms = 1; } #if defined(RTCLOCK) time_t rawtime; struct tm * timeinfo; time (&rawtime); timeinfo = localtime (&rawtime); if (timeinfo != nullptr) { struct gtm gti; gti.tm_sec = timeinfo->tm_sec; gti.tm_min = timeinfo->tm_min; gti.tm_hour = timeinfo->tm_hour; gti.tm_mday = timeinfo->tm_mday; gti.tm_mon = timeinfo->tm_mon; gti.tm_year = timeinfo->tm_year; gti.tm_wday = timeinfo->tm_wday; gti.tm_yday = timeinfo->tm_yday; g_rtcTime = gmktime(>i); } else { g_rtcTime = rawtime; } #endif #if defined(SIMU_EXCEPTIONS) signal(SIGFPE, sig); signal(SIGSEGV, sig); try { #endif // Init LCD callbacks lcdInit(); #if !defined(SIMU_BOOTLOADER) simuMain(); #else pthread_attr_t attr; pthread_attr_init(&attr); struct sched_param sp; sp.sched_priority = SCHED_RR; pthread_attr_setschedparam(&attr, &sp); pthread_t bl_pid; pthread_create(&bl_pid, &attr, &bootloaderThread, nullptr); #endif simu_running = true; #if defined(SIMU_EXCEPTIONS) } catch (...) { } #endif } extern RTOS_TASK_HANDLE mixerTaskId; extern RTOS_TASK_HANDLE menusTaskId; void simuStop() { if (!simu_running) return; simu_shutdown = true; pthread_join(mixerTaskId, nullptr); pthread_join(menusTaskId, nullptr); simu_running = false; } struct SimulatorAudio { int volumeGain; int currentVolume; uint16_t leftoverData[AUDIO_BUFFER_SIZE]; int leftoverLen; bool threadRunning; pthread_t threadPid; } simuAudio; bool simuIsRunning() { return simu_running; } uint8_t simuSleep(uint32_t ms) { for (uint32_t i = 0; i < ms; ++i){ if (simu_shutdown || !simu_running) return 1; sleep(1); } return 0; } void audioConsumeCurrentBuffer() { } void setScaledVolume(uint8_t volume) { simuAudio.currentVolume = 127 * volume * simuAudio.volumeGain / VOLUME_LEVEL_MAX / 10; // TRACE_SIMPGMSPACE("setVolume(): in: %u, out: %u", volume, simuAudio.currentVolume); } void setVolume(uint8_t volume) { } int32_t getVolume() { return 0; } #if defined(SIMU_AUDIO) void copyBuffer(uint8_t * dest, const uint16_t * buff, unsigned int samples) { for(unsigned int i=0; i= nextBuffer->size*2) { copyBuffer(stream, nextBuffer->data, nextBuffer->size); stream += nextBuffer->size*2; len -= nextBuffer->size*2; // putchar('+'); audioQueue.buffersFifo.freeNextFilledBuffer(); } else { //partial copyBuffer(stream, nextBuffer->data, len/2); simuAudio.leftoverLen = (nextBuffer->size-len/2); memcpy(simuAudio.leftoverData, &nextBuffer->data[len/2], simuAudio.leftoverLen*2); len = 0; // putchar('p'); audioQueue.buffersFifo.freeNextFilledBuffer(); break; } } else { break; } } } //fill the rest of buffer with silence if (len > 0) { SDL_memset(stream, 0x8000, len); // make sure this is silence. // putchar('.'); } } void * audioThread(void *) { /* Checking here if SDL audio was initialized is wrong, because the SDL_CloseAudio() de-initializes it. if ( !SDL_WasInit(SDL_INIT_AUDIO) ) { fprintf(stderr, "ERROR: couldn't initialize SDL audio support\n"); return 0; } */ SDL_AudioSpec wanted, have; /* Set the audio format */ wanted.freq = AUDIO_SAMPLE_RATE; wanted.format = AUDIO_S16SYS; wanted.channels = 1; /* 1 = mono, 2 = stereo */ wanted.samples = AUDIO_BUFFER_SIZE*2; /* Good low-latency value for callback */ wanted.callback = fillAudioBuffer; wanted.userdata = nullptr; /* SDL_OpenAudio() internally calls SDL_InitSubSystem(SDL_INIT_AUDIO), which initializes SDL Audio subsystem if necessary */ if ( SDL_OpenAudio(&wanted, &have) < 0 ) { fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError()); return nullptr; } SDL_PauseAudio(0); while (simuAudio.threadRunning) { audioQueue.wakeup(); sleep(1); } SDL_CloseAudio(); return nullptr; } void startAudioThread(int volumeGain) { simuAudio.leftoverLen = 0; simuAudio.threadRunning = true; simuAudio.volumeGain = volumeGain; TRACE_SIMPGMSPACE("startAudioThread(%d)", volumeGain); setScaledVolume(VOLUME_LEVEL_DEF); pthread_attr_t attr; pthread_attr_init(&attr); struct sched_param sp; sp.sched_priority = SCHED_RR; pthread_attr_setschedparam(&attr, &sp); pthread_create(&simuAudio.threadPid, &attr, &audioThread, nullptr); #ifdef __linux__ pthread_setname_np(simuAudio.threadPid, "audio"); #endif } void stopAudioThread() { simuAudio.threadRunning = false; pthread_join(simuAudio.threadPid, nullptr); } #endif // #if defined(SIMU_AUDIO) #if !defined(COLORLCD) void lcdSetRefVolt(uint8_t val) { } #endif void boardInit() { } uint32_t pwrCheck() { return simu_shutdown ? e_power_off : e_power_on; } bool pwrPressed() { return false; } bool pwrOffPressed() { #if defined(PWR_BUTTON_PRESS) return pwrPressed(); #else return !pwrPressed(); #endif } void pwrInit() {} void pwrOn() {} void pwrOff() {} bool UNEXPECTED_SHUTDOWN() { return false; } void SET_POWER_REASON(uint32_t value) {} #if defined(TRIMS_EMULATE_BUTTONS) bool trimsAsButtons = false; void setTrimsAsButtons(bool val) { trimsAsButtons = val; } bool getTrimsAsButtons() { bool lua = false; #if defined(LUA) lua = isLuaStandaloneRunning(); #endif return (trimsAsButtons || lua); } #endif uint32_t readKeys() { uint32_t result = 0; for (int i = 0; i < MAX_KEYS; i++) { if (keysStates[i]) { // TRACE("key pressed %d", i); result |= 1 << i; } } return result; } uint32_t readTrims() { uint32_t result = 0; for (int i = 0; i < keysGetMaxTrims() * 2; i++) { if (trimsStates[i]) { // TRACE("trim pressed %d", i); result |= 1 << i; } } #if defined(PCBXLITE) if (keysStates[KEY_SHIFT]) result = ((result & 0x03) << 6) | ((result & 0x0c) << 2); #endif return result; } int usbPlugged() { return false; } int getSelectedUsbMode() { return USB_JOYSTICK_MODE; } void setSelectedUsbMode(int mode) {} void delay_ms(uint32_t ms) { } void delay_us(uint16_t us) { } void unlockFlash() { } void lockFlash() { } void flashWrite(uint32_t *address, const uint32_t *buffer) { simuSleep(10); } uint32_t isBootloaderStart(const uint8_t * block) { return 1; } #if defined(PCBXLITES) bool isJackPlugged() { return false; } #endif void serialPrintf(const char * format, ...) { } void serialCrlf() { } void serialPutc(char c) { } void boardOff() { } void hapticOff() {} #if defined(PCBFRSKY) || defined(PCBFLYSKY) HardwareOptions hardwareOptions; #endif uint32_t Master_frequency = 0; uint32_t Current_used = 0; uint16_t Current_max = 0; void setSticksGain(uint8_t) { } uint16_t getCurrent() { return 10; } void calcConsumption() { } #if defined(HEADPHONE_TRAINER_SWITCH_GPIO) void enableHeadphone() { } void enableTrainer() { } void enableSpeaker() { } void disableSpeaker() { } #endif int trainerModuleSbusGetByte(unsigned char*) { return 0; } void rtcInit() { } void rtcGetTime(struct gtm * t) { } void rtcSetTime(const struct gtm * t) { } #if defined(USB_SERIAL) const etx_serial_port_t UsbSerialPort = { "USB-VCP", nullptr, nullptr }; #endif #if defined(AUX_SERIAL) || defined(AUX2_SERIAL) static void* _fake_drv_init(void*, const etx_serial_init*) { return (void*)1; } static void _fake_drv_fct1(void*) {} static void _fake_drv_send_byte(void*, uint8_t) {} static void _fake_drv_send_buffer(void*, const uint8_t*, uint32_t) {} static int _fake_drv_get_byte(void*, uint8_t*) { return 0; } static const etx_serial_driver_t _fake_drv = { .init = _fake_drv_init, .deinit = _fake_drv_fct1, .sendByte = _fake_drv_send_byte, .sendBuffer = _fake_drv_send_buffer, .txCompleted = nullptr, .waitForTxCompleted = nullptr, .enableRx = nullptr, .getByte = _fake_drv_get_byte, .getLastByte = nullptr, .clearRxBuffer = nullptr, .getBaudrate = nullptr, .setBaudrate = nullptr, .setPolarity = nullptr, .setHWOption = nullptr, .setReceiveCb = nullptr, .setIdleCb = nullptr, .setBaudrateCb = nullptr, }; #endif #if defined(AUX_SERIAL) #if defined(AUX_SERIAL_PWR_GPIO) static void _fake_pwr_aux(uint8_t) {} #define AUX_SERIAL_PWR _fake_pwr_aux #else #define AUX_SERIAL_PWR nullptr #endif static const etx_serial_port_t auxSerialPort = { "AUX1", &_fake_drv, nullptr, AUX_SERIAL_PWR }; #define AUX_SERIAL_PORT &auxSerialPort #else #define AUX_SERIAL_PORT nullptr #endif #if defined(AUX2_SERIAL) #if defined(AUX_SERIAL_PWR_GPIO) static void _fake_pwr_aux2(uint8_t) {} #define AUX2_SERIAL_PWR _fake_pwr_aux2 #else #define AUX2_SERIAL_PWR nullptr #endif static const etx_serial_port_t aux2SerialPort = { "AUX2", &_fake_drv, nullptr, AUX2_SERIAL_PWR }; #define AUX2_SERIAL_PORT &aux2SerialPort #else #define AUX2_SERIAL_PORT nullptr #endif // AUX2_SERIAL static const etx_serial_port_t* serialPorts[MAX_AUX_SERIAL] = { AUX_SERIAL_PORT, AUX2_SERIAL_PORT, }; const etx_serial_port_t* auxSerialGetPort(int port_nr) { if (port_nr >= MAX_AUX_SERIAL) return nullptr; return serialPorts[port_nr]; } #if defined(HARDWARE_TOUCH) struct TouchState simTouchState = {}; bool simTouchOccured = false; bool touchPanelEventOccured() { if(simTouchOccured) { simTouchOccured = false; return true; } return false; } struct TouchState touchPanelRead() { struct TouchState st = simTouchState; simTouchState.deltaX = 0; simTouchState.deltaY = 0; return st; } struct TouchState getInternalTouchState() { return simTouchState; } #endif void telemetryStart() {} void telemetryStop() {}