aboutsummaryrefslogtreecommitdiff
path: root/FanController/fan_controller/hw/fan.c
diff options
context:
space:
mode:
Diffstat (limited to 'FanController/fan_controller/hw/fan.c')
-rw-r--r--FanController/fan_controller/hw/fan.c338
1 files changed, 338 insertions, 0 deletions
diff --git a/FanController/fan_controller/hw/fan.c b/FanController/fan_controller/hw/fan.c
new file mode 100644
index 0000000..e2ac6d8
--- /dev/null
+++ b/FanController/fan_controller/hw/fan.c
@@ -0,0 +1,338 @@
+/* Autor: Ján Sučan <jan@jansucan.sk>
+ *
+ * Zdrojove kody, ich casti a subory z nich vzniknute priamo alebo nepriamo
+ * (objektove subory, Intel Hex, ...) prosim nepouzivajte komercne, ani ako
+ * sucast komercnych diel. Vsetky ostatne pripady pouzitia su dovolene.
+ *
+ * Please don't use the source codes, their parts and files created from them
+ * directly or indirectly, (object files, Intel Hex files, ...) for commercial
+ * purposes, not even as a part of commercial products. All other use cases
+ * are allowed.
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+#include <math.h>
+
+#include <fan_controller/hw/fan.h>
+#include <fan_controller/error.h>
+#include <fan_controller/median.h>
+#include <fan_controller/hw/led.h>
+#include <fan_controller/hw/io.h>
+#include <fan_controller/config.h>
+#include <fan_controller/delay.h>
+#include <fan_controller/hw/button.h>
+
+/** Pocet vzoriek z ktorych sa bude pocitat median pri merani sirky pulzu z tachometra ventilatora. */
+#define FAN_PULSE_SAMPLE_COUNT 7
+
+/** Pocet milisekund kolko sa bude cakat na ustalenie otacok pri novom nastaveni pri kalibracii merania otacok. */
+#define FAN_RPM_STABILIZATION_MS 10000U // = 10 s
+
+/** Pocet milisekund do kedy sa musi zachytit pulz tachometra pri merani. Inak sa bude povazovat ventilator za zastaveny a bude ohlasena chyba. */
+#define FAN_RPM_TIMER_MAX_VALUE 16000U // = 1,024 s
+
+static void fan_pwm_enable(void);
+static void fan_pwm_disable(void);
+static void fan_error(void);
+static void fan_rpm_set_raw(uint8_t rpm);
+static void fan_rpm_sense_calibrate(void);
+static uint16_t fan_get_pulse_time(void);
+static uint16_t fan_get_pulse_time_median(void);
+
+uint16_t fan_min_rpm_pulse_time;
+uint16_t fan_max_rpm_pulse_time;
+
+/** Minimalna nastavitelna hodnota otacok pre PWM. */
+#define FAN_PWM_MIN 1U
+
+/** Maximalna nastavitelna hodnota otacok pre PWM. */
+#define FAN_PWM_MAX 79U
+
+/** Pocet urovni otacok pre PWM. */
+#define FAN_PWM_LEVEL_COUNT (FAN_PWM_MAX - FAN_PWM_MIN + 1U)
+
+/** Reload hodnota casovaca pre generovanie PWM o frekvencii priblizne 25 kHz. */
+#define FAN_PWM_TIMER_RELOAD_VALUE 176U
+
+
+/**
+ * @brief Nastavenie otacok ventilatora cez PWM.
+ *
+ * @param rpm Hodnota otacok z rozsahu 0 az FAN_PWM_LEVEL_COUNT (vratane).
+ */
+void
+fan_rpm_set_raw(uint8_t rpm)
+{
+ if (rpm >= FAN_PWM_LEVEL_COUNT) {
+ // Hodnota PWM je mimo pripustneho rozsahu
+ error();
+ }
+ // Nastavenie output-compare pre generovanie PWM signalu
+ OCR0 = FAN_PWM_TIMER_RELOAD_VALUE + FAN_PWM_MIN + rpm;
+}
+
+/**
+ * @brief Nastavenie otacok ventilatora.
+ *
+ * @param rpm Hodnota otacok z rozsahu 0 az 255.
+ */
+void
+fan_rpm_set(uint8_t rpm)
+{
+ // Hodnota sa prepocita z rozsahu [0, 255] na [0, FAN_PWM_LEVEL_COUNT]
+ double ratio = ((double) (FAN_PWM_LEVEL_COUNT - 1)) / UINT8_MAX;
+
+ uint8_t v = ratio * rpm;
+
+ if (v >= FAN_PWM_LEVEL_COUNT) {
+ // Pretecenie pri prevode
+ v = FAN_PWM_LEVEL_COUNT - 1;
+ }
+ // Nastavenie otacok ventilatora
+ fan_rpm_set_raw(v);
+}
+
+/**
+ * @brief Obsluha prerusenia od casovaca pre generovanie PWM signalu pre ovladanie otacok.
+ *
+ * Naplni casovac hodnotou tak, aby sa generovala PWM o frekvencii priblizne 25 kHz.
+ */
+ISR(TIMER0_OVF_vect)
+{
+ TCNT0 = FAN_PWM_TIMER_RELOAD_VALUE;
+}
+
+/**
+ * @brief Inicializacia ventilatora.
+ *
+ * Zapne sa PWM, precita sa konfiguracia pre vypocet realnych otacok z EEPROM.
+ * Ak este nebola ziadna konfiguracia vytvorena, spusti sa kalibracia merania otacok.
+ * Kalibraciu meranie je mozne vyziadat vzdy stlacenim tlacitka pri resete zariadenia.
+ */
+void
+fan_init(void)
+{
+ fan_pwm_enable();
+
+ if (!config_is_fan_max_rpm_pulse_time_init() || !config_is_fan_min_rpm_pulse_time_init() || button_is_pressed()) {
+ // Kalibruje sa ked je hodnota neinicializovana alebo ked je kalibracia vyziadana uzivatelom
+ fan_rpm_sense_calibrate();
+ }
+ // Nacitaju sa casy pulzu na minimalnych a maximalnych otackach ventilatora
+ fan_min_rpm_pulse_time = config_fan_min_rpm_pulse_time_load();
+ fan_max_rpm_pulse_time = config_fan_max_rpm_pulse_time_load();
+}
+
+/**
+ * @brief Chyba ventilatora.
+ *
+ * Odpoji sa PWM vstup ventilatora a vyvola sa chyba.
+ */
+void
+fan_error(void)
+{
+ fan_pwm_disable();
+ io_disable_fan_pwm();
+ error();
+}
+
+/**
+ * @brief Kalibracia merania otacok ventilatora.
+ *
+ * Zmeria sa cas pulzu signalu tachometra pri minimalnych a pri maximalnych otackach.
+ * Postup merania sa indikuje stlpcom zelenych LED.
+ */
+void
+fan_rpm_sense_calibrate(void)
+{
+ // Cervenou LED indikovat kalibracny mod
+ led_red_on();
+ led_green_bar_show_by_mask(0);
+
+ // Cas, ktoreho uplunutie bude indikovat jedna zelena LED
+ const uint16_t time_unit = (2 * FAN_RPM_STABILIZATION_MS) / LED_GREEN_BAR_LED_COUNT;
+ // Pocet rozsvietenych LED pre cakanie na ustalenie minimalnych otacok
+ const uint8_t leds_for_one_wait = LED_GREEN_BAR_LED_COUNT / 2;
+ // Maska pre postupne rozsvecovanie zelenych LED pre indikaciu priebehu
+ uint8_t mask = 0x00;
+
+ // Ziska cas cas pulzu na minimalnych otackach
+ fan_rpm_set(0);
+ for (uint8_t i = 0; i < leds_for_one_wait; ++i) {
+ // Indikuje sa cas cakania
+ delay_ms(time_unit);
+ mask = ((mask << 1) | 0x01);
+ led_green_bar_show_by_mask(mask);
+ }
+ fan_min_rpm_pulse_time = fan_get_pulse_time_median();
+ config_fan_min_rpm_pulse_time_save(fan_min_rpm_pulse_time);
+
+ // Ziska sa cas pulzu na maximalnych otackach
+ fan_rpm_set(UINT8_MAX);
+ for (uint8_t i = 0; i < leds_for_one_wait; ++i) {
+ // Indikuje sa cas cakania
+ delay_ms(time_unit);
+ mask = ((mask << 1) | 0x01);
+ led_green_bar_show_by_mask(mask);
+ }
+ fan_max_rpm_pulse_time = fan_get_pulse_time_median();
+ // Namerane vysledky sa zapisu do EEPROM, aby sa nemusela kalibracia spustat pri dalsom zapnuti zariadenia
+ config_fan_max_rpm_pulse_time_save(fan_max_rpm_pulse_time);
+
+ // Nastavi sa kludova hodnota otacok = najnizsie otacky
+ fan_rpm_set(0);
+
+ // Indikacia ukoncenia kalibracneho rezimu
+ led_red_off();
+ led_green_bar_show_by_mask(0);
+}
+
+/**
+ * @brief Ziskanie relativnej realnej hodnoty otacok.
+ *
+ * Zmeria sa sirka pulzu z tachometra a z rozsahu [sirka_pulzu_pri_max_otackach, sirka_pulzu_pri_min_otackach]
+ * sa prepocita do rozsahu 0 az 255.
+ *
+ * @return Relativna hodnota otacok z rozsahu 0 az 255.
+ */
+uint8_t
+fan_rpm_get(void)
+{
+ const float pulse_time_range = (float) (fan_min_rpm_pulse_time - fan_max_rpm_pulse_time);
+
+ uint16_t pt = fan_get_pulse_time();
+ // Korekcia nameraneho casu pulzu do platneho rozsahu (rozsahu zmeranom pri kalibracii)
+ if (pt < fan_max_rpm_pulse_time) {
+ pt = fan_max_rpm_pulse_time;
+ } else if (pt > fan_min_rpm_pulse_time) {
+ pt = fan_min_rpm_pulse_time;
+ }
+
+ const float pt_relative = pt - fan_max_rpm_pulse_time;
+ // Pomer nameranej hodnoty sirky pulzu k celemu rozsahu sirky pulzu z tachometra
+ float ratio = pt_relative / pulse_time_range;
+ // Osetrenie pretecenia a podtecenie pri aritm. operaciach
+ if (ratio < 0.0) {
+ ratio = 0.0;
+ } else if (ratio > 1.0) {
+ ratio = 1.0;
+ }
+
+ // Aplikuje sa korekcia zobrazenia, aby merane otacky mali viac linearny priebeh
+ ratio = (ratio * 9.0) + 1.0;
+ ratio = log10(ratio);
+ ratio = (ratio * 9.0) + 1.0;
+ ratio = log10(ratio);
+ // Osetrenie pretecenia a podtecenie pri aritm. operaciach
+ if (ratio < 0.0) {
+ ratio = 0.0;
+ } else if (ratio > 1.0) {
+ ratio = 1.0;
+ }
+
+ return (UINT8_MAX - ((uint8_t) (((float) UINT8_MAX) * ratio)));
+}
+
+/**
+ * @brief Zmeranie sirky pulzu z tachometra ventilatora.
+ *
+ * Pri meranie sa detekuje zastavenie ventilatora a ak nastalo, vyvola sa chyba.
+ *
+ * @return Sirka pulzu z tachometra ventilatora.
+ */
+uint16_t
+fan_get_pulse_time(void)
+{
+ // Zacina sa s meranim sirky pulzu, casovac je zastaveny
+ TCNT1 = 0x0000;
+
+ // Pocka sa na nabeznu hranu
+ // Pocka sa na log. 0 RPM signalu
+ while (io_is_fan_tachometer_set()) {
+ // Mozne chyby pristup po 8-bitovych polovickach sa ignoruju
+ if (TCNT1 > FAN_RPM_TIMER_MAX_VALUE) {
+ // Detekovane zastavenie ventilatora
+ fan_error();
+ }
+ }
+
+ // Pocka sa na log. 1 RPM signalu
+ while (!io_is_fan_tachometer_set()) {
+ if (TCNT1 > FAN_RPM_TIMER_MAX_VALUE) {
+ // Detekovane zastavenie ventilatora
+ fan_error();
+ }
+ }
+
+ // Zapnutie casovaca 1, preddelicka 1024
+ TCCR1B |= ((1 << CS12) | (1 << CS10));
+ // Pocka sa na log. 0 RPM signalu
+ while (io_is_fan_tachometer_set()) {
+ if (TCNT1 > FAN_RPM_TIMER_MAX_VALUE) {
+ // Detekovane zastavenie ventilatora
+ fan_error();
+ }
+ }
+ // Konci s s meranim sirky pulzu, vypnutie casovaca 1
+ TCCR1B &= ~((1 << CS12) | (1 << CS10));
+
+ // Ziskanie nameranej hodnoty
+ return TCNT1;
+}
+
+/**
+ * @brief Zmeranie sirky pulzu z tachometra ventilatora vypocitaneho ako median z viacerych nameranych hodnot.
+ *
+ * Pri meranie sa detekuje zastavenie ventilatora a ak nastalo, vyvola sa chyba.
+ *
+ * @return Sirka pulzu z tachometra ventilatora vypocitana ako median z viacerych nameranych hodnot.
+ */
+uint16_t
+fan_get_pulse_time_median(void)
+{
+ median_buffer_t m;
+
+ median_init_buffer(&m, FAN_PULSE_SAMPLE_COUNT);
+ for (uint8_t i = 0; i < FAN_PULSE_SAMPLE_COUNT; i++) {
+ median_save_sample(&m, fan_get_pulse_time());
+ }
+ return median_get(&m);
+}
+
+/**
+ * @brief Aktivovanie generovania PWM signalu pre ovladanie otacok ventilatora.
+ *
+ * Nastavi sa Casovac 0 a jemu prislusny Output Compare register.
+ */
+void
+fan_pwm_enable(void)
+{
+ // 25 KHz perioda
+ TCNT0 = FAN_PWM_TIMER_RELOAD_VALUE;
+ // Povoli sa prerusenie od casovaca pre generovanie PWM
+ TIMSK |= 1 << TOIE0;
+ // Nastavi sa najnizsia kludova hodnota otacok
+ fan_rpm_set(0);
+ // Fast PWM mode
+ TCCR0 |= (1 << WGM01) | (1 << WGM00);
+ // Non-inverted PWM
+ TCCR0 |= (1 << COM01);
+ // Prescale by 8 = Zapnutie casovaca
+ TCCR0 |= (1 << CS01);
+}
+
+/**
+ * @brief Deaktivovanie generovania PWM signalu pre ovladanie otacok ventilatora.
+ *
+ * Zastavi sa Casovac 0 a odpoji sa vystup z Output Compare od GPIO pinu.
+ */
+void
+fan_pwm_disable(void)
+{
+ // Vypnutie casovaca
+ TCCR0 &= ~((1 << CS02) | (1 << CS01) | (1 << CS00));
+ // Odpojenie OC0
+ TCCR0 &= ~((1 << COM01) | (1 << COM00));
+} \ No newline at end of file