;=============================================================================== ; Title: Soldering Station ; ; Author: Rob Jansen, Copyright (c) 2021..2021, all rights reserved. ; ; Revisions ; --------- ; 2020-05-05 : Initial version ; ; Compiler: jalv25r5 ; ; Description: Controlling the temperature for a soldering station using the ; temperature sensor in the heating element. The soldering ; temperature can be set with a potentiometer and is displayed on ; in degrees using three 7-segment displays. ; Two preset buttons are available to store a temperature setting. ; ; Sources: - ; ; ------------------------------- Pragmas ------------------------------------- include 16f1829 ; target PICmicro include ss_display_driver ; Controlling the 7-segment displays include ss_adc_driver ; Converting the analog inputs to digital include ss_eeprom ; EEPROM library include ss_heater ; Heater library ; This program uses the internal oscillator at 32 MHz. pragma target clock 32_000_000 ; oscillator frequency pragma target OSC INTOSC_NOCLKOUT ; Internal Clock pragma target PLLEN ENABLED ; PLL on to get 32 MHz pragma target WDT CONTROL ; Use Watchdog, software controlled pragma target PWRTE ENABLED ; Power up timer enabled pragma target BROWNOUT DISABLED ; No brownout reset pragma target FCMEN DISABLED ; No clock monitoring pragma target IESO DISABLED ; int/ext osc. switch pragma target LVP DISABLED ; Not use low voltage programming we need the input pragma target MCLR INTERNAL ; Internal external, use as input ; Set the internal clock frequency to 32 MHz. OSCCON_IRCF = 0b1110 ; Set 32 MHz (uses 8 MHz source) OSCCON_SCS = 0b00 ; Clock determined by FOSC (32 MHz) ; Set watchdog to 256 ms. WDTCON_WDTPS = 0b01000 ; Enable weak pull up. WPUA = 0b0011_1111 WPUB = 0b1100_0000 ; No weak pull-up on analog inputs (RB4 and RB5) WPUC = 0b1111_1111 OPTION_REG_WPUEN = FALSE enable_digital_io() ; -------------------------- Constant declarations ----------------------------- ; Some timing values in microseconds. const LOOP_TIME = 10_000 ; We use it twice in the loop so loop total is 20 ms. const DEBOUNCE_TIME = 25_000 ; 25 ms. ; Time to press a preset key before the temperature is stored, expressed in ; DEBOUNCE_TIME. const PRESET_STORE_TIME = 80 ; 80 * DEBOUNCE_TIME = 2 seconds ; Display temperature ranges. const word TEMPERATURE_MIN = 200 ; Variable adjust minimum. const word TEMPERATURE_MAX = 450 ; Variable adjust maximum. ; Accept +/- 2 degrees hystersis on the set temperature before we display ; a change in the set temperature. For showing the actual versus set ; temperature we use 5 degrees Celsius. const word TEMPERATURE_SET_HYS = 2 ; Calibration ranges to convert ADC values to degrees Celcius. const word TEMPERATURE_CALIBRATE_MIN = 20 ; Room temperature calibration at 20 degrees (C-L). const word TEMPERATURE_CALIBRATE_MAX = 300 ; Second reference point, melting of lead solder (C-H). ; For calibration we work with ADC values. We accept a mimium and a maximum range. ; Calculation is as follows. The LM358 has a multiplication factor of 2.2 and the ; resistor in series with the sensor is 470 Ohm. The ADC is 10 bits over 5 Volt so ; we have 1024/5 = 205 steps per Volt. When you look at 2 different heater elements ; the sensor resistance at room temperature is different: ; -) For an A1321 this is 43 - 58 Ohm. This gives a minimal sensor voltage of ; (43 / (43 + 470)) * 5 Volt = 0.42 Volt so after the LM358 this is 0.92 Volt ; which equals and ADC value of 0.92 * 205 = 189 ; -) For an 133A this is 10 - 20 Ohm. This gives a minimal sensor voltage of ; (10 / (10 + 470)) * 5 Volt = 0.1 Volt so after the LM358 this is 0.23 Volt ; which equals an ADC value of 0.23 * 205 = 47. So we take a minimum of 30. ; The LM358 cannot output more than 3.5 Volt or an ADC value of 717. So ; we take a maximum of 700. The sensor resistance at 3.5 Volt would be at ; most as follows. Vsensor would be 3.5 / 2.2 = 1.6 Volt so the maximum sensor ; resistor can be calculated as 1.6 / 5 = 0.32 --> 0.32 = X / (X + 470) --> ; 0.32 * (X + 470) = X --> X = (0.32 * X) + (0.32 * 470) --> ; X = (0.32 * X) + 150 --> 0.68 X = 150 --> X = 150 / 0.68 = 220 Ohm const word ADC_CAL_MIN = 30 const word ADC_CAL_MAX = 700 ; ADC ranges. 10-bit range over 5 Volt. const word ADC_MIN = 0 const word ADC_MAX = 1023 ; Time for an error before it is displayed expressed in LOOP_TIME. const byte ERROR_TIME = 100 ; 100 * (2 * LOOP_TIME) = 2 seconds. ; Timer value to show a new set temperature. const byte TIMER_DISPLAY_SET_TEMPERATURE = 150 ; 150 * (2 * LOOP_TIME) is 3 seconds. ; Operating modes. const byte SETTING_VARIABLE = 0 const byte SETTING_PRESET_1 = 1 const byte SETTING_PRESET_2 = 2 ; Average constant. We use this to stabilize the actual and set ; temperatures by averaging several measurements. const byte AVERAGE_COUNT_ACTUAL = 40 ; Actual temperature is less stable. const byte AVERAGE_COUNT_SET = 20 ; If the information is available, already set the initial values. This ; will prevent that the PIC starts with a calibration mode after being ; re-programmed (EEPROM is then erased). The calibration values here ; are based on a measurement using a Hakko 907 soldering iron. const CALIBRATION_PRESET = TRUE if defined(CALIBRATION_PRESET) then pragma eedata 0x2C, 1, ; Setting 1 initial value 300 degrees Celsius. 0x5E, 1, ; Setting 2 initial value 350 degrees Celsius. 0xDE, 0, ; Calibration low in ADC value, 222 equals 20 degrees. 0x90, 1, ; Calibration high in ADC value, 400 equals 300 degrees. 0 ; Last setting stored is variable operating mode. end if ; -------------------------- Variable declarations ----------------------------- ; Some variables we will initialize later to speed up the float calculations. ; We average some temperatures to get a more stable behaviour. var word average_for_actual_temperature[AVERAGE_COUNT_ACTUAL] var word average_for_set_temperature[AVERAGE_COUNT_SET] ; Note that the temperatures here are in degrees Celcius. var word set_temperature = TEMPERATURE_MIN var word actual_temperature = TEMPERATURE_MIN var word previous_actual_temperature = TEMPERATURE_MIN var word new_set_temperature = TEMPERATURE_MIN var word new_actual_temperature = TEMPERATURE_MIN var word display_actual_temperature = TEMPERATURE_MIN ; The calibration values are in ADC values. Initial value not relevant. var word calibration_setting_high = 0 var word calibration_setting_low = 0 var word adc_sensor_data = 0 var byte display_timer = TIMER_DISPLAY_SET_TEMPERATURE var byte sensor_error_counter = ERROR_TIME ; Do not show errors at start-up. var byte heater_error_counter = ERROR_TIME ; Do not show errors at start-up. var byte operation_mode = SETTING_VARIABLE var bit set_temperature_changed = TRUE ; Will display the set temperature/ var bit capture_potentiometer = TRUE ; Start measuring the potentiometer. var bit capture_sensor = FALSE var bit heater_error = FALSE var bit sensor_error = FALSE var bit key_was_released = TRUE ; ------------------------ Functions & Procedures ----------------------------- ; Wait for a longer time. Two seconds in this case. procedure wait_some_time() is _usec_delay(2_000_000) end procedure ; Debounce time for a pressed key. procedure key_debounce() is _usec_delay(DEBOUNCE_TIME) end procedure ; Kick the watchdog. Must be done to prevent a reset. procedure kick_the_watchdog() is assembler clrwdt end assembler end procedure ; Calculate the avarage set temperature and use the new temperature as new ; reference. Calculation is done over the last AVERAGE_COUNT measurements. function get_average_set_temperature(word in new_temperature) return word is var word new_average = 0 var byte index for (AVERAGE_COUNT_SET - 1) using index loop new_average = new_average + average_for_set_temperature[index] average_for_set_temperature[index] = average_for_set_temperature[index + 1] end loop average_for_set_temperature[AVERAGE_COUNT_SET - 1] = new_temperature ; Calculate new average. new_average = new_average / (AVERAGE_COUNT_SET - 1) return new_average end function ; Calculate the average actual temperature and use the new temperature as new ; reference. Calculation is done over the last AVERAGE_COUNT measurements. function get_average_actual_temperature(word in new_temperature) return word is var word new_average = 0 var byte index for (AVERAGE_COUNT_ACTUAL - 1) using index loop new_average = new_average + average_for_actual_temperature[index] average_for_actual_temperature[index] = average_for_actual_temperature[index + 1] end loop average_for_actual_temperature[AVERAGE_COUNT_ACTUAL - 1] = new_temperature ; Calculate new average. new_average = new_average / (AVERAGE_COUNT_ACTUAL - 1) return new_average end function ; Convert the given ADC value from the sensor to a temperature. We need the calibrated ; values to convert the ADC sensor figures to a temperature. We know that the ADC value ; of calibration_setting_low equals TEMPERATURE_CALIBRATE_MIN degrees and the ADC value ; of calibration_setting_high equals TEMPERATURE_CALIBRATE_MAX degrees. We need float ; calculation to prevent rounding errors. This routine takes about 1.5 ms. function adc_sensor_to_temperature(word in adc_value) return word is var float temperature_calibration_max = float(TEMPERATURE_CALIBRATE_MAX) var float temperature_calibration_min = float(TEMPERATURE_CALIBRATE_MIN) var float calibration_high = float(calibration_setting_high) var float calibration_low = float(calibration_setting_low) var float new_adc var float temperature var float degree_per_adc_step ; Since we start from a minimum calibration temperature, the given value may ; not be lower than that. If lower then we use the minimum. if adc_value < calibration_setting_low then adc_value = calibration_setting_low end if new_adc = float(adc_value) degree_per_adc_step = (temperature_calibration_max - temperature_calibration_min) / (calibration_high - calibration_low) temperature = ((new_adc - calibration_low) * degree_per_adc_step) + temperature_calibration_min return word(temperature) end function ; Convert the given ADC value from the potentionmeter to a temperature. We know that the ; potentionmeter uses the full range from ADC_MIN to ADC_MAX where ADC_MIN equals ; TEMPERATURE_MIN and ADC_MAX equals TEMPERATURE_MAX. We need float calculation ; to prevent rounding errors. This routine takes about 1.8 ms. function adc_potentiometer_to_temperature(word in adc_value) return word is var float conversion_factor conversion_factor = float(ADC_MAX - ADC_MIN) / float(TEMPERATURE_MAX - TEMPERATURE_MIN) conversion_factor = (float(adc_value) / conversion_factor) + float(TEMPERATURE_MIN) return word(conversion_factor) end function ; Erase the arrays that are used to average the temperature values. procedure initialize_average_arrays() is var byte index ; The set temperature ranges from TEMPERATURE_MIN to TEMPERATURE_MAX so ; initialze with TEMPERATURE_MIN. for AVERAGE_COUNT_SET using index loop average_for_set_temperature[index] = TEMPERATURE_MIN end loop ; The actual temperature starts at room temperature so initializing ; it 0 is OK. for AVERAGE_COUNT_ACTUAL using index loop average_for_actual_temperature[index] = 0 end loop end procedure ; Reset sensor error and heater error. procedure error_reset() is sensor_error = FALSE heater_error = FALSE sensor_error_counter = ERROR_TIME heater_error_counter = ERROR_TIME end procedure ; ------------------------ Main program starts here --------------------------- ; We must include the part for handling the keys and the calibration first. This ; is a part of the main program. include ss_keys_and_calibration ; Init used libraries and clear the average temperature arrays.. display_init() adc_init() heater_init() ; Give the hardware some time to stabilize. _usec_delay(100_000) ; Clear the sliding average arrays. The may be used by the calibration function. initialize_average_arrays() ; Enable peripheral and global interrupts. INTCON_PEIE = TRUE INTCON_GIE = TRUE ; First check if the soldering station was calibrated. If not we need to activate it. ; We cannot continue if the cabration is not done. calibration_setting_high = eeprom_read_word(EEPROM_ADDRESS_CALIBRATION_HIGH) calibration_setting_low = eeprom_read_word(EEPROM_ADDRESS_CALIBRATION_LOW) ; The calibration high value can never be higher than the maximum ADC steps. Also ; the low calibration can never be equal or higher then the high calibration. if (calibration_setting_high == EEPROM_NO_WORD_DATA) | (calibration_setting_low == EEPROM_NO_WORD_DATA) | (calibration_setting_low >= calibration_setting_high) then ; We need to force a calibration cycle. handle_calibration(TRUE) else ; Only (re-) start a calibration when the right keys are pressed at power-up. handle_calibration(FALSE) end if ; Clear the sliding average arrays again since they can be used by the ; calibration function. Also reset the set temperatures since the calibration ; routine may have set these to the ADC values. initialize_average_arrays() set_heater_target_temperature(TEMPERATURE_MIN) set_heater_current_temperature(TEMPERATURE_MIN) ; Get the calibration values again in case they have changed by the calibration. calibration_setting_high = eeprom_read_word(EEPROM_ADDRESS_CALIBRATION_HIGH) calibration_setting_low = eeprom_read_word(EEPROM_ADDRESS_CALIBRATION_LOW) ; Check again if still not right show a calibration error and halt there. if (calibration_setting_high == EEPROM_NO_WORD_DATA) | (calibration_setting_low == EEPROM_NO_WORD_DATA) | (calibration_setting_low >= calibration_setting_high) then ; This is not correct we cannot continue without proper calibration. display_calibrate_error() forever loop ; Do nothing, power up can escape from this. end loop end if ; Check which operation mode was chosen last time. operation_mode = eeprom_read_byte(EEPROM_ADDRESS_OPERATION_MODE) if (operation_mode > SETTING_PRESET_2) then ; No valid setting so we set it to variable mode. operation_mode = SETTING_VARIABLE eeprom_write_byte(EEPROM_ADDRESS_OPERATION_MODE, operation_mode) else ; There was a setting get the preset temperature. if (operation_mode == SETTING_PRESET_1) then new_set_temperature = eeprom_read_word(EEPROM_ADDRESS_PRESET_1) elsif (operation_mode == SETTING_PRESET_2) then new_set_temperature = eeprom_read_word(EEPROM_ADDRESS_PRESET_2) else new_set_temperature = TEMPERATURE_MIN end if ; Only set the temperature if there is valid setting. if (new_set_temperature >= TEMPERATURE_MIN) & (new_set_temperature <= TEMPERATURE_MAX) then set_temperature = new_set_temperature ; Show this value on the display and set it as target for the heater. set_temperature_changed = TRUE end if end if ; Now wakeup (enable) the watchdog. WDTCON_SWDTEN = TRUE ; Main loop. forever Loop ; We use a total of 20 ms looptime for all our timing. Half the time here ; and half the time at the end. This is done for the ADC to give sufficient ; time for the inputs to settle when the ADC input is changed. _usec_delay(LOOP_TIME) kick_the_watchdog() ; Handle the keys. if key_was_released then handle_keys() end if ; Check if ADC data is available and see which one it was. if adc_data_available then if capture_sensor then adc_sensor_data = adc_get_value() ; We where capturing the temperature sensor, convert ADC value to temperature. new_actual_temperature = adc_sensor_to_temperature(adc_sensor_data) ; Remember the previous actual temperature for heater error detection. previous_actual_temperature = actual_temperature ; We do take a small average of the actual_temperature for stabilization. actual_temperature = (actual_temperature + new_actual_temperature) / 2 set_heater_current_temperature(actual_temperature) ; Average the display temperature to make it even more stable. display_actual_temperature = get_average_actual_temperature(actual_temperature) elsif capture_potentiometer then ; We where capturing the potentionmeter, convert ADC value to temperature. new_set_temperature = adc_potentiometer_to_temperature(adc_get_value()) ; Average the display temperature to keep it even more stable. new_set_temperature = get_average_set_temperature(new_set_temperature) ; Compare the value but take some hysteresis to prevent a constant switch ; between actual temperature and the set temperature. if (new_set_temperature > (set_temperature + TEMPERATURE_SET_HYS)) | (new_set_temperature < (set_temperature - TEMPERATURE_SET_HYS)) then set_temperature = new_set_temperature ; Indicate later that the new set temperature must be shown on the display ; and then also set the new target temperature for the heater. set_temperature_changed = TRUE end if end if end if ; Get a new adc reading value. We toggle between sensor and potentiometer. Note ; that we only switch the input here and start the AD conversion after the loop ; time. This gives the ADC sufficient time for the input to settle. if adc_ready() then if capture_potentiometer then adc_select_sensor_input() capture_sensor = TRUE capture_potentiometer = FALSE elsif capture_sensor then ; Only switch to potentiometer if we use the variable setting. if (operation_mode == SETTING_VARIABLE) then adc_select_potentiometer_input() capture_sensor = FALSE capture_potentiometer = TRUE else ; Keep the sensor input mode. adc_select_sensor_input() end if end if end if ; Check for sensor error. Once error always an error. Can be reset by ; the variable setting key. if (adc_sensor_data < ADC_CAL_MIN) | (adc_sensor_data > ADC_CAL_MAX) then ; Sensor error. It can never give such a low or high value. if sensor_error_counter == 0 then sensor_error = TRUE display_sensor_error() heater_disable() else sensor_error_counter = sensor_error_counter - 1 end if else sensor_error_counter = ERROR_TIME end if ; Check for heater error. Sensor error goes first. Once error always an error. ; Can be reset by the variable setting key. if !sensor_error & heater_is_busy() & (actual_temperature <= previous_actual_temperature) then ; Heater error. Iron not warmimg up. if heater_error_counter == 0 then heater_error = TRUE display_heater_error() heater_disable() else heater_error_counter = heater_error_counter - 1 end if else heater_error_counter = ERROR_TIME end if ; Update the display when no error is present. if !sensor_error & !heater_error then ; No errors, enable heater and determine what we need to display. heater_enable() if set_temperature_changed then ; We adjusted the temperature, show this and not the actual temperature. display_temperature(set_temperature) display_timer = TIMER_DISPLAY_SET_TEMPERATURE ; Also set the heater to our new set temperature. set_heater_target_temperature(set_temperature) set_temperature_changed = FALSE else ; Show the actual temperature and later the set temperature. if display_timer != 0 then display_timer = display_timer - 1 else ; The display actual temperature is not very stable on the display. ; If the set temperature is close to the actual temperature we show ; the actual temperature otherwise we show the set temperature. if heater_on_target() then display_temperature(set_temperature) else display_temperature(display_actual_temperature) end if end if end if end if ; Second half of the 10ms wait loop. _usec_delay(LOOP_TIME) kick_the_watchdog() ; We start a new AD conversion after and before the loop time so ; that the ADC input had sufficient time to stabilize. adc_start_conversion() ; Check if the keys where release to prevent repetition. if (key_preset_1 == KEY_RELEASED) & (key_preset_2 == KEY_RELEASED) & (key_variable == KEY_RELEASED) then key_was_released = TRUE end if end loop