// By Ben V. Brown - V2.0 of the TS100 firmware #include #include #include #include "LIS2DH12.hpp" #include #include #include "Settings.h" #include "Translation.h" #include "cmsis_os.h" #include "stdlib.h" #include "stm32f1xx_hal.h" #include "string.h" #define ACCELDEBUG 0 uint8_t PCBVersion = 0; // File local variables uint16_t currentlyActiveTemperatureTarget = 0; uint32_t lastMovementTime = 0; uint32_t lastButtonTime = 0; int16_t idealQCVoltage = 0; // FreeRTOS variables osThreadId GUITaskHandle; osThreadId PIDTaskHandle; osThreadId MOVTaskHandle; static TaskHandle_t pidTaskNotification = NULL; void startGUITask(void const *argument); void startPIDTask(void const *argument); void startMOVTask(void const *argument); // End FreeRTOS // Main sets up the hardware then hands over to the FreeRTOS kernel int main(void) { /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); Setup_HAL(); // Setup all the HAL objects HAL_IWDG_Refresh(&hiwdg); setTipMilliWatts(0); // force tip off FRToSI2C::init(&hi2c1); OLED::initialize(); // start up the LCD OLED::setFont(0); // default to bigger font // Testing for which accelerometer is mounted uint8_t buffer[1]; HAL_IWDG_Refresh(&hiwdg); if (HAL_I2C_Mem_Read(&hi2c1, 29 << 1, 0x0F, I2C_MEMADD_SIZE_8BIT, buffer, 1, 1000) == HAL_OK) { PCBVersion = 1; MMA8652FC::initalize(); // this sets up the I2C registers } else if (HAL_I2C_Mem_Read(&hi2c1, 25 << 1, 0x0F, I2C_MEMADD_SIZE_8BIT, buffer, 1, 1000) == HAL_OK) { PCBVersion = 2; // Setup the ST Accelerometer LIS2DH12::initalize(); // startup the accelerometer } else { PCBVersion = 3; systemSettings.SleepTime = 0; systemSettings.ShutdownTime = 0; // No accel -> disable sleep systemSettings.sensitivity = 0; saveSettings(); } HAL_IWDG_Refresh(&hiwdg); restoreSettings(); // load the settings from flash setCalibrationOffset(systemSettings.CalibrationOffset); setTipType((enum TipType) systemSettings.tipType, systemSettings.customTipGain); // apply tip type selection HAL_IWDG_Refresh(&hiwdg); /* Create the thread(s) */ /* definition and creation of GUITask */ osThreadDef(GUITask, startGUITask, osPriorityBelowNormal, 0, 768); // 3k GUITaskHandle = osThreadCreate(osThread(GUITask), NULL); /* definition and creation of PIDTask */ osThreadDef(PIDTask, startPIDTask, osPriorityRealtime, 0, 512); // 2k PIDTaskHandle = osThreadCreate(osThread(PIDTask), NULL); if (PCBVersion < 3) { /* definition and creation of MOVTask */ osThreadDef(MOVTask, startMOVTask, osPriorityNormal, 0, 512); // 2k MOVTaskHandle = osThreadCreate(osThread(MOVTask), NULL); } /* Start scheduler */ osKernelStart(); /* We should never get here as control is now taken by the scheduler */ while (1) { } } void printVoltage() { OLED::printNumber(getInputVoltageX10(systemSettings.voltageDiv) / 10, 2); OLED::drawChar('.'); OLED::printNumber(getInputVoltageX10(systemSettings.voltageDiv) % 10, 1); } void GUIDelay() { // Called in all UI looping tasks, // This limits the re-draw rate to the LCD and also lets the DMA run // As the gui task can very easily fill this bus with transactions, which will // prevent the movement detection from running osDelay(50); } void gui_drawTipTemp(bool symbol) { // Draw tip temp handling unit conversion & tolerance near setpoint uint16_t Temp = getTipRawTemp(0); if (systemSettings.temperatureInF) Temp = tipMeasurementToF(Temp); else Temp = tipMeasurementToC(Temp); OLED::printNumber(Temp, 3); // Draw the tip temp out finally if (symbol) { if (systemSettings.temperatureInF) OLED::print("F"); else OLED::print("C"); } } ButtonState getButtonState() { /* * Read in the buttons and then determine if a state change needs to occur */ /* * If the previous state was 00 Then we want to latch the new state if * different & update time * If the previous state was !00 Then we want to search if we trigger long * press (buttons still down), or if release we trigger press * (downtime>filter) */ static uint8_t previousState = 0; static uint32_t previousStateChange = 0; const uint16_t timeout = 40; uint8_t currentState; currentState = ( HAL_GPIO_ReadPin(KEY_A_GPIO_Port, KEY_A_Pin) == GPIO_PIN_RESET ? 1 : 0) << 0; currentState |= ( HAL_GPIO_ReadPin(KEY_B_GPIO_Port, KEY_B_Pin) == GPIO_PIN_RESET ? 1 : 0) << 1; if (currentState) lastButtonTime = xTaskGetTickCount(); if (currentState == previousState) { if (currentState == 0) return BUTTON_NONE; if ((xTaskGetTickCount() - previousStateChange) > timeout) { // User has been holding the button down // We want to send a buttong is held message if (currentState == 0x01) return BUTTON_F_LONG; else if (currentState == 0x02) return BUTTON_B_LONG; else return BUTTON_NONE; // Both being held case, we dont long hold this } else return BUTTON_NONE; } else { // A change in button state has occurred ButtonState retVal = BUTTON_NONE; if (currentState) { // User has pressed a button down (nothing done on down) if (currentState != previousState) { // There has been a change in the button states // If there is a rising edge on one of the buttons from double press we // want to mask that out As users are having issues with not release // both at once if (previousState == 0x03) currentState = 0x03; } } else { // User has released buttons // If they previously had the buttons down we want to check if they were < // long hold and trigger a press if ((xTaskGetTickCount() - previousStateChange) < timeout) { // The user didn't hold the button for long // So we send button press if (previousState == 0x01) retVal = BUTTON_F_SHORT; else if (previousState == 0x02) retVal = BUTTON_B_SHORT; else retVal = BUTTON_BOTH; // Both being held case } } previousState = currentState; previousStateChange = xTaskGetTickCount(); return retVal; } return BUTTON_NONE; } void waitForButtonPress() { // we are just lazy and sleep until user confirms button press // This also eats the button press event! ButtonState buttons = getButtonState(); while (buttons) { buttons = getButtonState(); GUIDelay(); GUIDelay(); } while (!buttons) { buttons = getButtonState(); GUIDelay(); GUIDelay(); } } void waitForButtonPressOrTimeout(uint32_t timeout) { timeout += xTaskGetTickCount(); // calculate the exit point ButtonState buttons = getButtonState(); while (buttons) { buttons = getButtonState(); GUIDelay(); if (xTaskGetTickCount() > timeout) return; } while (!buttons) { buttons = getButtonState(); GUIDelay(); if (xTaskGetTickCount() > timeout) return; } } #ifdef MODEL_TS100 // returns true if undervoltage has occured static bool checkVoltageForExit() { uint16_t v = getInputVoltageX10(systemSettings.voltageDiv); if ((v < lookupVoltageLevel(systemSettings.cutoutSetting))) { OLED::clearScreen(); OLED::setCursor(0, 0); if (systemSettings.detailedSoldering) { OLED::setFont(1); OLED::print(UndervoltageString); OLED::setCursor(0, 8); OLED::print(InputVoltageString); printVoltage(); OLED::print("V"); } else { OLED::setFont(0); OLED::print(UVLOWarningString); } OLED::refresh(); currentlyActiveTemperatureTarget = 0; waitForButtonPress(); return true; } return false; } #endif static void gui_drawBatteryIcon() { #ifdef MODEL_TS100 if (systemSettings.cutoutSetting) { // User is on a lithium battery // we need to calculate which of the 10 levels they are on uint8_t cellCount = systemSettings.cutoutSetting + 2; uint16_t cellV = getInputVoltageX10(systemSettings.voltageDiv) / cellCount; // Should give us approx cell voltage X10 // Range is 42 -> 33 = 9 steps therefore we will use battery 1-10 if (cellV < 33) cellV = 33; cellV -= 33; // Should leave us a number of 0-9 if (cellV > 9) cellV = 9; OLED::drawBattery(cellV + 1); } else OLED::drawSymbol(15); // Draw the DC Logo #else // On TS80 we replace this symbol with the voltage we are operating on // If <9V then show single digit, if not show duals uint8_t V = getInputVoltageX10(systemSettings.voltageDiv); if (V % 10 >= 5) V = V / 10 + 1;// round up else V = V / 10; if (V >= 10) { int16_t xPos = OLED::getCursorX(); OLED::setFont(1); OLED::printNumber(1, 1); OLED::setCursor(xPos, 8); OLED::printNumber(V % 10, 1); OLED::setFont(0); OLED::setCursor(xPos+12,0); // need to reset this as if we drew a wide char } else { OLED::printNumber(V, 1); } #endif } static void gui_solderingTempAdjust() { uint32_t lastChange = xTaskGetTickCount(); currentlyActiveTemperatureTarget = 0; uint32_t autoRepeatTimer = 0; uint8_t autoRepeatAcceleration = 0; for (;;) { OLED::setCursor(0, 0); OLED::clearScreen(); OLED::setFont(0); ButtonState buttons = getButtonState(); if (buttons) lastChange = xTaskGetTickCount(); switch (buttons) { case BUTTON_NONE: // stay break; case BUTTON_BOTH: // exit return; break; case BUTTON_B_LONG: if (xTaskGetTickCount() - autoRepeatTimer + autoRepeatAcceleration > PRESS_ACCEL_INTERVAL_MAX) { systemSettings.SolderingTemp -= 10; // sub 10 autoRepeatTimer = xTaskGetTickCount(); autoRepeatAcceleration += PRESS_ACCEL_STEP; } break; case BUTTON_F_LONG: if (xTaskGetTickCount() - autoRepeatTimer + autoRepeatAcceleration > PRESS_ACCEL_INTERVAL_MAX) { systemSettings.SolderingTemp += 10; autoRepeatTimer = xTaskGetTickCount(); autoRepeatAcceleration += PRESS_ACCEL_STEP; } break; case BUTTON_F_SHORT: systemSettings.SolderingTemp += 10; // add 10 break; case BUTTON_B_SHORT: systemSettings.SolderingTemp -= 10; // sub 10 break; default: break; } if ((PRESS_ACCEL_INTERVAL_MAX - autoRepeatAcceleration) < PRESS_ACCEL_INTERVAL_MIN) { autoRepeatAcceleration = PRESS_ACCEL_INTERVAL_MAX - PRESS_ACCEL_INTERVAL_MIN; } // constrain between 50-450 C if (systemSettings.temperatureInF) { if (systemSettings.SolderingTemp > 850) systemSettings.SolderingTemp = 850; if (systemSettings.SolderingTemp < 120) systemSettings.SolderingTemp = 120; } else { if (systemSettings.SolderingTemp > 450) systemSettings.SolderingTemp = 450; if (systemSettings.SolderingTemp < 50) systemSettings.SolderingTemp = 50; } if (xTaskGetTickCount() - lastChange > 200) return; // exit if user just doesn't press anything for a bit #ifdef MODEL_TS80 if (!OLED::getRotation()) #else if (OLED::getRotation()) #endif OLED::drawChar('-'); else OLED::drawChar('+'); OLED::drawChar(' '); OLED::printNumber(systemSettings.SolderingTemp, 3); if (systemSettings.temperatureInF) OLED::drawSymbol(0); else OLED::drawSymbol(1); OLED::drawChar(' '); #ifdef MODEL_TS80 if (!OLED::getRotation()) #else if (OLED::getRotation()) #endif OLED::drawChar('+'); else OLED::drawChar('-'); OLED::refresh(); GUIDelay(); } } static uint16_t min(uint16_t a, uint16_t b) { if (a > b) return b; else return a; } static int gui_SolderingSleepingMode() { // Drop to sleep temperature and display until movement or button press for (;;) { ButtonState buttons = getButtonState(); if (buttons) return 0; if ((xTaskGetTickCount() - lastMovementTime < 100) || (xTaskGetTickCount() - lastButtonTime < 100)) return 0; // user moved or pressed a button, go back to soldering #ifdef MODEL_TS100 if (checkVoltageForExit()) return 1; // return non-zero on error #endif if (systemSettings.temperatureInF) { currentlyActiveTemperatureTarget = ftoTipMeasurement( min(systemSettings.SleepTemp, systemSettings.SolderingTemp)); } else { currentlyActiveTemperatureTarget = ctoTipMeasurement( min(systemSettings.SleepTemp, systemSettings.SolderingTemp)); } // draw the lcd uint16_t tipTemp; if (systemSettings.temperatureInF) tipTemp = tipMeasurementToF(getTipRawTemp(0)); else tipTemp = tipMeasurementToC(getTipRawTemp(0)); OLED::clearScreen(); OLED::setCursor(0, 0); if (systemSettings.detailedSoldering) { OLED::setFont(1); OLED::print(SleepingAdvancedString); OLED::setCursor(0, 8); OLED::print(SleepingTipAdvancedString); OLED::printNumber(tipTemp, 3); if (systemSettings.temperatureInF) OLED::print("F"); else OLED::print("C"); OLED::print(" "); printVoltage(); OLED::drawChar('V'); } else { OLED::setFont(0); OLED::print(SleepingSimpleString); OLED::printNumber(tipTemp, 3); if (systemSettings.temperatureInF) OLED::drawSymbol(0); else OLED::drawSymbol(1); } if (systemSettings.ShutdownTime) // only allow shutdown exit if time > 0 if (lastMovementTime) if (((uint32_t) (xTaskGetTickCount() - lastMovementTime)) > (uint32_t) (systemSettings.ShutdownTime * 60 * 100)) { // shutdown currentlyActiveTemperatureTarget = 0; return 1; // we want to exit soldering mode } OLED::refresh(); GUIDelay(); } return 0; } static void display_countdown(int sleepThres) { /* * Print seconds or minutes (if > 99 seconds) until sleep * mode is triggered. */ int lastEventTime = lastButtonTime < lastMovementTime ? lastMovementTime : lastButtonTime; int downCount = sleepThres - xTaskGetTickCount() + lastEventTime; if (downCount > 9900) { OLED::printNumber(downCount / 6000 + 1, 2); OLED::print("M"); } else { OLED::printNumber(downCount / 100 + 1, 2); OLED::print("S"); } } static void gui_solderingMode(uint8_t jumpToSleep) { /* * * Soldering (gui_solderingMode) * -> Main loop where we draw temp, and animations * --> User presses buttons and they goto the temperature adjust screen * ---> Display the current setpoint temperature * ---> Use buttons to change forward and back on temperature * ---> Both buttons or timeout for exiting * --> Long hold front button to enter boost mode * ---> Just temporarily sets the system into the alternate temperature for * PID control * --> Long hold back button to exit * --> Double button to exit */ bool boostModeOn = false; uint32_t sleepThres = 0; if (systemSettings.SleepTime < 6) sleepThres = systemSettings.SleepTime * 10 * 100; else sleepThres = (systemSettings.SleepTime - 5) * 60 * 100; for (;;) { uint16_t tipTemp = getTipRawTemp(0); ButtonState buttons = getButtonState(); switch (buttons) { case BUTTON_NONE: // stay boostModeOn = false; break; case BUTTON_BOTH: // exit return; break; case BUTTON_B_LONG: return; // exit on back long hold break; case BUTTON_F_LONG: // if boost mode is enabled turn it on if (systemSettings.boostModeEnabled) boostModeOn = true; break; case BUTTON_F_SHORT: case BUTTON_B_SHORT: { uint16_t oldTemp = systemSettings.SolderingTemp; gui_solderingTempAdjust(); // goto adjust temp mode if (oldTemp != systemSettings.SolderingTemp) { saveSettings(); // only save on change } } break; default: break; } // else we update the screen information OLED::setCursor(0, 0); OLED::clearScreen(); OLED::setFont(0); if (tipTemp > 32752) { OLED::print(BadTipString); OLED::refresh(); currentlyActiveTemperatureTarget = 0; waitForButtonPress(); return; } else { if (systemSettings.detailedSoldering) { OLED::setFont(1); OLED::print(SolderingAdvancedPowerPrompt); // Power: OLED::printNumber(milliWattHistory[0] / 1000, 2); OLED::drawChar('.'); OLED::printNumber(milliWattHistory[0] / 100 % 10, 1); OLED::drawChar('W'); if (systemSettings.sensitivity && systemSettings.SleepTime) { OLED::print(" "); display_countdown(sleepThres); } OLED::setCursor(0, 8); OLED::print(SleepingTipAdvancedString); gui_drawTipTemp(true); OLED::print(" "); printVoltage(); OLED::drawChar('V'); } else { // We switch the layout direction depending on the orientation of the // OLED:: if (OLED::getRotation()) { // battery gui_drawBatteryIcon(); OLED::drawChar(' '); // Space out gap between battery <-> temp gui_drawTipTemp(true); // Draw current tip temp // We draw boost arrow if boosting, or else gap temp <-> heat // indicator if (boostModeOn) OLED::drawSymbol(2); else OLED::drawChar(' '); // Draw heating/cooling symbols OLED::drawHeatSymbol(getTipPWM()); } else { // Draw heating/cooling symbols OLED::drawHeatSymbol(getTipPWM()); // We draw boost arrow if boosting, or else gap temp <-> heat // indicator if (boostModeOn) OLED::drawSymbol(2); else OLED::drawChar(' '); gui_drawTipTemp(true); // Draw current tip temp OLED::drawChar(' '); // Space out gap between battery <-> temp gui_drawBatteryIcon(); } } } OLED::refresh(); // Update the setpoints for the temperature if (boostModeOn) { if (systemSettings.temperatureInF) currentlyActiveTemperatureTarget = ftoTipMeasurement( systemSettings.BoostTemp); else currentlyActiveTemperatureTarget = ctoTipMeasurement( systemSettings.BoostTemp); } else { if (systemSettings.temperatureInF) currentlyActiveTemperatureTarget = ftoTipMeasurement( systemSettings.SolderingTemp); else currentlyActiveTemperatureTarget = ctoTipMeasurement( systemSettings.SolderingTemp); } #ifdef MODEL_TS100 // Undervoltage test if (checkVoltageForExit()) { lastButtonTime = xTaskGetTickCount(); return; } #else // on the TS80 we only want to check for over voltage to prevent tip damage if (getInputVoltageX10(systemSettings.voltageDiv) > 150) { lastButtonTime = xTaskGetTickCount(); return; // Over voltage } #endif if (jumpToSleep) { if (gui_SolderingSleepingMode()) { lastButtonTime = xTaskGetTickCount(); return; // If the function returns non-0 then exit } } if (systemSettings.sensitivity && systemSettings.SleepTime) if (xTaskGetTickCount() - lastMovementTime > sleepThres && xTaskGetTickCount() - lastButtonTime > sleepThres) { if (gui_SolderingSleepingMode()) { lastButtonTime = xTaskGetTickCount(); return; // If the function returns non-0 then exit } } GUIDelay(); } } static const char *HEADERS[] = { __DATE__, "Heap: ", "HWMG: ", "HWMP: ", "HWMM: ", "Time: ", "Move: ", "RTip: ", "CTip: ", "Vin :", "THan: ", "Model: ", #ifdef MODEL_TS80 "QCV: ", #else "Ralim-", #endif }; void showVersion(void) { uint8_t screen = 0; ButtonState b; for (;;) { OLED::clearScreen(); // Ensure the buffer starts clean OLED::setCursor(0, 0); // Position the cursor at the 0,0 (top left) OLED::setFont(1); // small font #ifdef MODEL_TS100 OLED::print((char *) "V2.06 TS100"); // Print version number #else OLED::print((char *) "V2.06 TS80"); // Print version number #endif OLED::setCursor(0, 8); // second line OLED::print(HEADERS[screen]); switch (screen) { case 1: OLED::printNumber(xPortGetFreeHeapSize(), 5); break; case 2: OLED::printNumber(uxTaskGetStackHighWaterMark(GUITaskHandle), 5); break; case 3: OLED::printNumber(uxTaskGetStackHighWaterMark(PIDTaskHandle), 5); break; case 4: OLED::printNumber(uxTaskGetStackHighWaterMark(MOVTaskHandle), 5); break; case 5: OLED::printNumber(xTaskGetTickCount() / 100, 5); break; case 6: OLED::printNumber(lastMovementTime / 100, 5); break; case 7: OLED::printNumber(getTipRawTemp(0), 6); break; case 8: OLED::printNumber(tipMeasurementToC(getTipRawTemp(0)), 5); break; case 9: printVoltage(); break; case 10: OLED::printNumber(getHandleTemperature(), 3); break; case 11: OLED::printNumber(PCBVersion, 1); // Print PCB ID number break; case 12: #ifdef MODEL_TS80 OLED::printNumber(idealQCVoltage,3); #else OLED::print("Tek.com"); #endif break; default: break; } OLED::refresh(); b = getButtonState(); if (b == BUTTON_B_SHORT) return; else if (b == BUTTON_F_SHORT) { screen++; screen = screen % 13; } GUIDelay(); } } /* StartGUITask function */ void startGUITask(void const *argument __unused) { FRToSI2C::FRToSInit(); uint8_t tempWarningState = 0; bool buttonLockout = false; bool tempOnDisplay = false; getTipRawTemp(1); // reset filter OLED::setRotation(!(systemSettings.OrientationMode & 1)); uint32_t ticks = xTaskGetTickCount(); ticks += 400; // 4 seconds from now while (xTaskGetTickCount() < ticks) { if (showBootLogoIfavailable() == false) ticks = xTaskGetTickCount(); ButtonState buttons = getButtonState(); if (buttons) ticks = xTaskGetTickCount(); // make timeout now so we will exit GUIDelay(); } if (systemSettings.autoStartMode) { // jump directly to the autostart mode if (systemSettings.autoStartMode == 1) gui_solderingMode(0); if (systemSettings.autoStartMode == 2) gui_solderingMode(1); } #if ACCELDEBUG for (;;) { HAL_IWDG_Refresh(&hiwdg); osDelay(100); } //^ Kept here for a way to block this thread #endif for (;;) { ButtonState buttons = getButtonState(); if (tempWarningState == 2) buttons = BUTTON_F_SHORT; if (buttons != BUTTON_NONE && buttonLockout) buttons = BUTTON_NONE; else buttonLockout = false; switch (buttons) { case BUTTON_NONE: // Do nothing break; case BUTTON_BOTH: // Not used yet // In multi-language this might be used to reset language on a long hold // or some such break; case BUTTON_B_LONG: // Show the version information showVersion(); break; case BUTTON_F_LONG: gui_solderingTempAdjust(); saveSettings(); break; case BUTTON_F_SHORT: OLED::setFont(0); OLED::displayOnOff(true); // turn lcd on #ifdef MODEL_TS80 //Here we re-check for tip presence if (idealQCVoltage < 90) idealQCVoltage = calculateMaxVoltage(systemSettings.cutoutSetting); seekQC(idealQCVoltage,systemSettings.voltageDiv); #endif gui_solderingMode(0); // enter soldering mode buttonLockout = true; break; case BUTTON_B_SHORT: OLED::setFont(0); OLED::displayOnOff(true); // turn lcd on enterSettingsMenu(); // enter the settings menu saveSettings(); buttonLockout = true; setCalibrationOffset(systemSettings.CalibrationOffset); // ensure cal offset is applied break; default: break; } currentlyActiveTemperatureTarget = 0; // ensure tip is off uint16_t tipTemp = tipMeasurementToC(getTipRawTemp(0)); if (tipTemp < 50) { if (systemSettings.sensitivity) { if ((xTaskGetTickCount() - lastMovementTime) > 6000 && (xTaskGetTickCount() - lastButtonTime) > 6000) { OLED::displayOnOff(false); // turn lcd off when no movement } else OLED::displayOnOff(true); // turn lcd on } else OLED::displayOnOff(true); // turn lcd on - disabled motion sleep } else OLED::displayOnOff(true); // turn lcd on when temp > 50C if (tipTemp > 600) tipTemp = 0; // Clear the lcd buffer OLED::clearScreen(); OLED::setCursor(0, 0); if (systemSettings.detailedIDLE) { OLED::setFont(1); if (tipTemp > 470) { OLED::print(TipDisconnectedString); } else { OLED::print(IdleTipString); if (systemSettings.temperatureInF) OLED::printNumber(tipMeasurementToF(getTipRawTemp(0)), 3); else OLED::printNumber(tipMeasurementToC(getTipRawTemp(0)), 3); OLED::print(IdleSetString); OLED::printNumber(systemSettings.SolderingTemp, 3); } OLED::setCursor(0, 8); OLED::print(InputVoltageString); printVoltage(); } else { OLED::setFont(0); #ifdef MODEL_TS80 if (!OLED::getRotation()) { #else if (OLED::getRotation()) { #endif OLED::drawArea(12, 0, 84, 16, idleScreenBG); OLED::setCursor(0, 0); gui_drawBatteryIcon(); } else { OLED::drawArea(0, 0, 84, 16, idleScreenBGF); // Needs to be flipped so button ends up // on right side of screen OLED::setCursor(84, 0); gui_drawBatteryIcon(); } if (tipTemp > 55) tempOnDisplay = true; else if (tipTemp < 45) tempOnDisplay = false; if (tempOnDisplay) { // draw temp over the start soldering button // Location changes on screen rotation #ifdef MODEL_TS80 if (!OLED::getRotation()) { #else if (OLED::getRotation()) { #endif // in right handed mode we want to draw over the first part OLED::fillArea(55, 0, 41, 16, 0); // clear the area for the temp OLED::setCursor(56, 0); } else { OLED::fillArea(0, 0, 41, 16, 0); // clear the area OLED::setCursor(0, 0); } // draw in the temp OLED::setFont(0); // big font if (!(systemSettings.coolingTempBlink && (xTaskGetTickCount() % 50 < 25))) gui_drawTipTemp(false); // draw in the temp } } OLED::refresh(); GUIDelay(); } } /* StartPIDTask function */ void startPIDTask(void const *argument __unused) { /* * We take the current tip temperature & evaluate the next step for the tip * control PWM * Tip temperature is measured by getTipTemperature(1) so we get instant * result * This comes in Cx10 format * We then control the tip temperature to aim for the setpoint in the settings * struct * */ setTipMilliWatts(0); // disable the output driver if the output is set to be off #ifdef MODEL_TS80 idealQCVoltage = calculateMaxVoltage(systemSettings.cutoutSetting); #endif uint8_t rawC = ctoTipMeasurement(101) - ctoTipMeasurement(100); // 1*C change in raw. currentlyActiveTemperatureTarget = 0; // Force start with no output (off). If in sleep / soldering this will // be over-ridden rapidly history tempError = {{0}, 0, 0}; pidTaskNotification = xTaskGetCurrentTaskHandle(); for (;;) { if (ulTaskNotifyTake(pdTRUE, 50)) { // Wait a max of 50ms // This is a call to block this thread until the ADC does its samples uint16_t rawTemp = getTipRawTemp(1); // get instantaneous reading if (currentlyActiveTemperatureTarget) { // Cap the max set point to 450C if (currentlyActiveTemperatureTarget > ctoTipMeasurement(450)) { currentlyActiveTemperatureTarget = ctoTipMeasurement(450); } // As we get close to our target, temp noise causes the system // to be unstable. Use a rolling average to dampen it. // We overshoot by roughly 1/2 of 1 degree Fahrenheit. // This helps stabilize the display. int32_t tError = currentlyActiveTemperatureTarget - rawTemp + rawC/4; tError = tError > INT16_MAX ? INT16_MAX : tError; tError = tError < INT16_MIN ? INT16_MIN : tError; tempError.update(tError); // Now for the PID! int32_t milliWattsOut = 0; // P term - total power needed to hit target temp next cycle. // thermal mass = 1690 milliJ/*C for my tip. // = Watts*Seconds to raise Temp from room temp to +100*C, divided by 100*C. // divided by 20 to let I term dominate near set point. // I should retune this, but don't want to do it until // the feed-forward temp adjustment is in place. const uint16_t mass = 1690 / 20; int32_t milliWattsNeeded = tempToMilliWatts(tempError.average(), mass, rawC); milliWattsOut += milliWattsNeeded; // I term - energy needed to compensate for heat loss. // We track energy put into the system over some window. // Assuming the temp is stable, energy in = energy transfered. // (If it isn't, P will dominate). milliWattsOut += milliWattHistory.average(); // D term - use sudden temp change to counter fast cooling/heating. // In practice, this provides an early boost if temp is dropping // and counters extra power if the iron is no longer losing temp. // basically: temp - lastTemp // Unfortunately, our temp signal is too noisy to really help. setTipMilliWatts(milliWattsOut); } else { setTipMilliWatts(0); } HAL_IWDG_Refresh(&hiwdg); } else { if (currentlyActiveTemperatureTarget == 0) { setTipMilliWatts(0); } } } } #define MOVFilter 8 void startMOVTask(void const *argument __unused) { OLED::setRotation(true); #ifdef MODEL_TS80 startQC(systemSettings.voltageDiv); while (idealQCVoltage == 0) osDelay(20); // To ensure we return after idealQCVoltage is setup seekQC(idealQCVoltage,systemSettings.voltageDiv);// this will move the QC output to the preferred voltage to start with #else osDelay(250); // wait for accelerometer to stabilize #endif OLED::setRotation(!(systemSettings.OrientationMode & 1)); lastMovementTime = 0; int16_t datax[MOVFilter] = { 0 }; int16_t datay[MOVFilter] = { 0 }; int16_t dataz[MOVFilter] = { 0 }; uint8_t currentPointer = 0; int16_t tx = 0, ty = 0, tz = 0; int32_t avgx = 0, avgy = 0, avgz = 0; if (systemSettings.sensitivity > 9) systemSettings.sensitivity = 9; #if ACCELDEBUG uint32_t max = 0; #endif Orientation rotation = ORIENTATION_FLAT; for (;;) { int32_t threshold = 1500 + (9 * 200); threshold -= systemSettings.sensitivity * 200; // 200 is the step size if (PCBVersion == 2) { LIS2DH12::getAxisReadings(&tx, &ty, &tz); rotation = LIS2DH12::getOrientation(); } else if (PCBVersion == 1) { MMA8652FC::getAxisReadings(&tx, &ty, &tz); rotation = MMA8652FC::getOrientation(); } if (systemSettings.OrientationMode == 2) { if (rotation != ORIENTATION_FLAT) { OLED::setRotation(rotation == ORIENTATION_LEFT_HAND); // link the data through } } datax[currentPointer] = (int32_t) tx; datay[currentPointer] = (int32_t) ty; dataz[currentPointer] = (int32_t) tz; currentPointer = (currentPointer + 1) % MOVFilter; // calculate averages for (uint8_t i = 0; i < MOVFilter; i++) { avgx += datax[i]; avgy += datay[i]; avgz += dataz[i]; } avgx /= MOVFilter; avgy /= MOVFilter; avgz /= MOVFilter; // Sum the deltas int32_t error = (abs(avgx - tx) + abs(avgy - ty) + abs(avgz - tz)); #if ACCELDEBUG // Debug for Accel OLED::setFont(1); OLED::setCursor(0, 0); OLED::printNumber(abs(avgx - (int32_t)tx), 5); OLED::print(" "); OLED::printNumber(abs(avgy - (int32_t)ty), 5); if (error > max) { max = (abs(avgx - tx) + abs(avgy - ty) + abs(avgz - tz)); } OLED::setCursor(0, 8); OLED::printNumber(max, 5); OLED::print(" "); OLED::printNumber((abs(avgx - tx) + abs(avgy - ty) + abs(avgz - tz)), 5); OLED::refresh(); if (HAL_GPIO_ReadPin(KEY_A_GPIO_Port, KEY_A_Pin) == GPIO_PIN_RESET) { max = 0; } #endif // So now we have averages, we want to look if these are different by more // than the threshold // If error has occurred then we update the tick timer if (error > threshold) { lastMovementTime = xTaskGetTickCount(); } osDelay(100); // Slow down update rate #ifdef MODEL_TS80 if (currentlyActiveTemperatureTarget) { seekQC(idealQCVoltage,systemSettings.voltageDiv); // Run the QC seek again to try and compensate for cable V drop } #endif } } #define FLASH_LOGOADDR \ (0x8000000 | 0xF800) /*second last page of flash set aside for logo image*/ bool showBootLogoIfavailable() { // check if the header is there (0xAA,0x55,0xF0,0x0D) // If so display logo // TODO REDUCE STACK ON THIS ONE, USE DRAWING IN THE READ LOOP uint16_t temp[98]; for (uint8_t i = 0; i < (98); i++) { temp[i] = *(uint16_t *) (FLASH_LOGOADDR + (i * 2)); } uint8_t temp8[98 * 2]; for (uint8_t i = 0; i < 98; i++) { temp8[i * 2] = temp[i] >> 8; temp8[i * 2 + 1] = temp[i] & 0xFF; } if (temp8[0] != 0xAA) return false; if (temp8[1] != 0x55) return false; if (temp8[2] != 0xF0) return false; if (temp8[3] != 0x0D) return false; OLED::drawArea(0, 0, 96, 16, (uint8_t *) (temp8 + 4)); OLED::refresh(); return true; } /* * Catch the IRQ that says that the conversion is done on the temperature * readings coming in Once these have come in we can unblock the PID so that it * runs again */ void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (hadc == &hadc1) { if (pidTaskNotification) { vTaskNotifyGiveFromISR(pidTaskNotification, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } } void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c __unused) { FRToSI2C::CpltCallback(); } void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c __unused) { FRToSI2C::CpltCallback(); } void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c __unused) { FRToSI2C::CpltCallback(); } void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c __unused) { FRToSI2C::CpltCallback(); } void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c __unused) { FRToSI2C::CpltCallback(); } void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c __unused) { FRToSI2C::CpltCallback(); } void vApplicationStackOverflowHook(xTaskHandle *pxTask __unused, signed portCHAR *pcTaskName __unused) { // We dont have a good way to handle a stack overflow at this point in time NVIC_SystemReset(); }