/* Reflow Oven Controller
*
* Copyright (C) 2020  Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller 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.
*
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/

#include <reflow-controller/oven-driver.h>
#include <reflow-controller/periph-config/oven-driver-hwcfg.h>
#include <stm-periph/clock-enable-manager.h>
#include <reflow-controller/systick.h>

static struct pid_controller oven_pid;

static struct oven_pid_status oven_pid_current_status = {
	.active = false,
	.aborted = false,
	.target_temp = 0.0f,
	.current_temp = 0.0f,
	.timestamp_last_run = 0ULL
};

void oven_driver_init()
{
	rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(OVEN_CONTROLLER_PORT_RCC_MASK));
	rcc_manager_enable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(OVEN_CONTROLLER_TIM_RCC_MASK));

	OVEN_CONTROLLER_PORT->MODER &= MODER_DELETE(OVEN_CONTROLLER_PIN);
	OVEN_CONTROLLER_PORT->MODER |= ALTFUNC(OVEN_CONTROLLER_PIN);
	SETAF(OVEN_CONTROLLER_PORT, OVEN_CONTROLLER_PIN, OVEN_CONTROLLER_PIN_ALTFUNC);

	OVEN_CONTROLLER_PWM_TIMER->CR2 = 0UL;

	OVEN_CONTROLLER_PWM_TIMER->CCMR2 = TIM_CCMR2_OC3M;
	OVEN_CONTROLLER_PWM_TIMER->CCER = TIM_CCER_CC3E | TIM_CCER_CC3P;

	OVEN_CONTROLLER_PWM_TIMER->ARR = 1000U;
	OVEN_CONTROLLER_PWM_TIMER->PSC = 42000U - 1U;

	OVEN_CONTROLLER_PWM_TIMER->CR1 = TIM_CR1_CMS | TIM_CR1_CEN;
}

void oven_driver_set_power(uint8_t power)
{
	if (power > 100U)
		power = 100U;

	OVEN_CONTROLLER_PWM_TIMER->CCR3 = power * 10;
}

void oven_driver_disable()
{
	OVEN_CONTROLLER_PWM_TIMER->CR1 = 0UL;
	OVEN_CONTROLLER_PWM_TIMER->CR2 = 0UL;
	rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(OVEN_CONTROLLER_PORT_RCC_MASK));
	rcc_manager_disable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(OVEN_CONTROLLER_TIM_RCC_MASK));
}

void oven_pid_init(struct pid_controller *controller_to_copy)
{
	pid_copy(&oven_pid, controller_to_copy);
	oven_pid.output_sat_min = 0.0f;
	oven_pid.output_sat_max = 100.0f;
	oven_pid_current_status.timestamp_last_run = 0ULL;
	oven_pid_current_status.active = true;
}

void oven_pid_handle(float target_temp, float current_temp)
{
	float pid_out;

	if (oven_pid_current_status.active) {
		if (systick_ticks_have_passed(oven_pid_current_status.timestamp_last_run,
					      (uint64_t)(oven_pid.sample_period * 1000))) {
			pid_out = pid_sample(&oven_pid, target_temp - current_temp);
			oven_driver_set_power((uint8_t)pid_out);
			oven_pid_current_status.timestamp_last_run = systick_get_global_tick();
			oven_pid_current_status.active = true;
			oven_pid_current_status.target_temp = target_temp;
			oven_pid_current_status.current_temp = current_temp;
		}
	}
}

void oven_pid_report_error()
{
	oven_pid_current_status.active = false;
	oven_pid_current_status.aborted = true;
}

const struct oven_pid_status *oven_pid_get_status()
{
	return &oven_pid_current_status;
}

void oven_pid_stop()
{
	oven_pid_current_status.active = false;
	oven_pid_current_status.target_temp = 0.0f;
	oven_pid_current_status.current_temp = 0.0f;
}
