trennfix/sw: Added smokefix, further modularized the mm decoder
[eisenbahn.git] / trennfix / sw / src / smokefix_main.c
diff --git a/trennfix/sw/src/smokefix_main.c b/trennfix/sw/src/smokefix_main.c
new file mode 100644 (file)
index 0000000..594f77f
--- /dev/null
@@ -0,0 +1,731 @@
+/******************************************************************************
+ *
+ *  Trennfix firmware - main.c
+ *
+ *  Copyright (C) 2017 Philipp Hachtmann
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <avr/io.h>
+#include <avr/eeprom.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+
+#include <util/delay.h>
+#include <stdint.h>
+#include <mm/mm_switch.h>
+#include <mm/mm_decode.h>
+#include <config/hardware.h>
+
+uint8_t eeFooByte EEMEM = 123;
+
+#define EE_MAGIC 0xab
+
+FUSES = {
+       .low = FUSE_CKDIV8 & FUSE_SUT0 &\
+       FUSE_CKSEL3 & FUSE_CKSEL2 & FUSE_CKSEL1,
+       .high = FUSE_SPIEN & FUSE_BODLEVEL1 & FUSE_BODLEVEL0 & FUSE_EESAVE,
+       .extended = EFUSE_DEFAULT,
+};
+
+void mm_key_cb(uint8_t decoder, uint8_t function, uint8_t key);
+void mm_drive_cb(uint8_t decoder, uint8_t function, uint8_t command);
+
+enum op_mode {
+       OM_MOMENTARY,     /* on as long as "key on" pressed   */
+       OM_DOUBLE,        /* On off with "on" and "off" keys  */
+       OM_TOGGLE,        /* toggle on "key on" pressed       */
+       OM_ERASED = 0xff  /* EEPROM erased, need setup        */
+};
+
+enum learn_mode {
+       LM_OFF = 0,
+       LM_LEARN_ON_KEY,     /* Learn primary key                           */
+       LM_LEARN_OFF_KEY,    /* Learn secondary key, relevant for OM_DOUBLE */
+       LM_LEARN_INITIAL,    /* Learn initial pulse length, 10ms steps      */
+       LM_LEARN_DUTY,       /* Learn duty cycle 0-10                       */
+       LM_LEARN_OP_MODE,    /* Learn operation mode                        */
+       LM_EASY_WAIT_PRESS,  /* Wait for key press to enter easy mode       */
+       LM_EASY_WAIT_UP,     /* Wait for end of key press                   */
+       LM_EASY_WAIT_TURN,   /* Wait for loco 80 turn end                   */
+       LM_EASY_MODE,        /* Easy config mode for PWM and initial kick   */
+       LM_END,              /* Only a label                                */
+};
+
+struct config {
+       uint8_t magic; /* Magic value */
+       enum op_mode op_mode;
+       uint8_t decoder_on;
+       uint8_t key_on;
+       uint8_t decoder_off;
+       uint8_t key_off;
+       uint8_t initial_pulse; /* Lenghth of initial pulse in 10ms steps   */
+       uint8_t on_duty_cycle[15]; /* Duty cycle for on. 0-10                  */
+        volatile enum learn_mode  learn_mode;
+};
+
+static uint8_t main_speed = 0;
+static struct EEMEM config ee_config;
+static volatile struct config config;
+
+static volatile uint8_t easy_mode = 0;
+static volatile uint8_t easy_mode_possible = 0;
+
+#ifndef USE_REGISTER_VARS
+static volatile uint8_t drive_on = 0;
+#endif
+
+/******************************************************************************
+ *
+ * Some nice sounds to play on your coil
+ *
+ */
+
+#define G 180
+#define sekunde(g)(quinte((quinte(g)))*2)
+#define terz(g) ((g) * 4 / 5)
+#define kleine_terz(g) ((g) * 5 / 6)
+#define quarte(g) ((g) * 3 / 4)
+#define quinte(g) ((g) * 2 / 3)
+#define tt(g) ((g) * 32 / 45)
+#define septime(g) ((g) * 15 / 8)
+
+#if defined(WITH_SOUND) && defined(WITH_PWM)
+void play_tone(uint8_t divisor, uint8_t duration, uint8_t pause)
+{
+       uint16_t c;
+       TCCR1 = 0x8;
+       OCR1C = divisor;
+       c = (divisor * 2) / 3;
+       OCR1B = c;
+       for (c = 0; c < duration - pause; c++)
+               _delay_ms(2);
+       TCCR1 = 0x6;
+       OCR1C = PWM_CYCLE;
+       OCR1B = 12;
+       for (c = 0; c < pause; c++)
+               _delay_ms(2);
+       TCCR1 = 0x6;
+       OCR1C = PWM_CYCLE;
+       OCR1B = PWM_CYCLE;
+}
+
+static void tone_enter(void)
+{
+       play_tone((G), 70, 20);
+       play_tone(terz(G), 70, 20);
+       play_tone(quinte(G), 70, 20);
+       play_tone(G/2, 100, 0);
+}
+
+static void tone_good(void)
+{
+       play_tone(G, 150, 120);
+       play_tone(terz(G), 100, 70);
+       play_tone(tt(G), 100, 50);
+       play_tone(quarte(terz((G))), 50, 0);
+       play_tone(quinte(G), 150, 0);
+       play_tone(terz(G), 100, 50);
+}
+
+static void snd_on(void)
+{
+       TCCR1 = 0x8;
+       OCR1C = 120;
+       OCR1B = 60;
+}
+
+static void snd_off(void)
+{
+       TCCR1 = 0x6;
+       OCR1C = PWM_CYCLE;
+       OCR1B = PWM_CYCLE;
+}
+
+#else
+#define play_tone(...)
+#define tone_enter(...) {setpin(PIN_LED, 1); _delay_ms(500); setpin(PIN_LED, 0);}
+#define tone_good(...)
+#define snd_on(...)
+#define snd_off(...)
+#endif
+
+static void load_config(void)
+{
+       eeprom_read_block((uint8_t *)&config, &ee_config, sizeof(config));
+}
+
+static void save_config(void)
+{
+#ifdef WITH_EEPROM_UPDATE
+       eeprom_update_block((uint8_t *)&config, &ee_config, sizeof(config));
+#else
+       eeprom_write_block((uint8_t *)&config, &ee_config, sizeof(config));
+#endif
+}
+
+void mm_pinchange_callback(void)
+{
+       static uint8_t btn_last = 0;
+
+       if (BTN_PRESSED && !btn_last) {
+               config.learn_mode++;
+               config.learn_mode %= LM_END;
+       }
+       btn_last = BTN_PRESSED;
+}
+
+static uint8_t get_speed(uint8_t command)
+{
+       uint8_t b0, b2, b4, b6;
+
+       b0 = ((command & 0x80) != 0);
+       b2 = ((command & 0x20) != 0);
+       b4 = ((command & 0x8) != 0);
+       b6 = ((command & 0x2) != 0);
+
+       //if ((b9!= b2) || (b2 != b3) || (b3 != b4) || (b5 != b6) || (b7 != b8))
+       //      return 0xff;
+       return (b0+ b2 * 2 + b4 * 4 +b6 * 8);
+}
+
+enum mm2_command {
+       CF_MM1 ,
+       CF_REVERSE,
+       CF_FORWARD,
+       CF_F1_ON,
+       CF_F1_OFF,
+       CF_F2_ON,
+       CF_F2_OFF,
+       CF_F3_ON,
+       CF_F3_OFF,
+       CF_F4_ON,
+       CF_F4_OFF,
+};
+
+#ifdef MM_USE_QUEUE
+static void dequeue_commands(void)
+{
+
+       return;
+
+       struct mm_command *cmd = mm_get();
+       if (!cmd)
+               return;
+       if (cmd->recmode != MM_FAST)
+               return;
+       if ((cmd->function & 0x3) != 0x3)
+               return;
+       //      uint8_t loco = mm_lookup_decoder(cmd->address);
+
+}
+
+#endif
+
+void mm_drive_cb(uint8_t loco, uint8_t func_enc, uint8_t cmd_enc)
+{
+       uint8_t b1, b3, b5, b7;
+       uint8_t fcode;
+
+       /* Those three are decoded from fun and command */
+       uint8_t speed;
+       uint8_t function;
+       enum mm2_command mm2_command = CF_MM1;
+       static uint8_t alert_last = 0;
+       speed = get_speed(cmd_enc);
+
+#ifdef MUELL
+       goto mm2_done; // FIXME
+
+       b1 = ((cmd_enc & 0x4) != 0);
+       b3 = ((cmd_enc & 0x1) != 0);
+       b5 = ((cmd_enc & 0x4) != 0);
+       b7 = ((cmd_enc & 0x1) != 0);
+
+       fcode = b1 * 8 + b3 * 4 + b5 *2 + b7;
+
+       /* The ugly speed section */
+       if (speed < 8) {
+               if (fcode == 0xb) {
+                       mm2_command = CF_REVERSE;
+                       goto mm2_done;
+               }
+               if (fcode == 0x5) {
+                       mm2_command = CF_FORWARD;
+                       goto mm2_done;
+               }
+       } else {
+               if (fcode == 0xa) {
+                       mm2_command = CF_REVERSE;
+                       goto mm2_done;
+               }
+               if (fcode == 0x4) {
+                       mm2_command = CF_FORWARD;
+                       goto mm2_done;
+               }
+       }
+
+       /* Special cases for f1-f4  commands */
+       if (fcode == 0xa) {
+               switch(speed) {
+               case 3:
+                       mm2_command = CF_F1_ON;
+                       goto mm2_done;
+               case 4:
+                       mm2_command = CF_F2_ON;
+                       goto mm2_done;
+               case 6:
+                       mm2_command = CF_F3_ON;
+                       goto mm2_done;
+               case 7:
+                       mm2_command = CF_F4_ON;
+                       goto mm2_done;
+               default:
+                       goto mm2_done;
+               }
+       } else if (fcode == 0x5) {
+               switch(speed) {
+                       case 11:
+                               mm2_command = CF_F1_OFF;
+                               goto mm2_done;
+                       case 12:
+                               mm2_command = CF_F2_OFF;
+                               goto mm2_done;
+                       case 14:
+                               mm2_command = CF_F3_OFF;
+                               goto mm2_done;
+                       case 15:
+                               mm2_command = CF_F4_OFF;
+                               goto mm2_done;
+               }
+       }
+
+       switch(fcode) {
+       case 0xc:
+               mm2_command = CF_F1_OFF;
+               goto mm2_done;
+       case 0xd:
+               mm2_command = CF_F1_ON;
+               goto mm2_done;
+
+       case 0x2:
+               mm2_command = CF_F2_OFF;
+               goto mm2_done;
+       case 0x3:
+               mm2_command = CF_F2_ON;
+               goto mm2_done;
+
+       case 0x6:
+               mm2_command = CF_F3_OFF;
+               goto mm2_done;
+
+       case 0x7:
+               mm2_command = CF_F3_ON;
+               goto mm2_done;
+
+       case 0xe:
+               mm2_command = CF_F4_OFF;
+               goto mm2_done;
+
+       case 0xf:
+               mm2_command = CF_F4_ON;
+               goto mm2_done;
+       default:
+               break;
+       }
+ mm2_done:
+       if (loco == 34) {
+               switch(mm2_command) {
+               case CF_F3_ON:
+                       drive_on = 1;
+                       break;
+               case CF_F3_OFF:
+                       drive_on = 0;
+                       break;
+               default:
+                       break;
+               }
+       }
+#endif
+       if (loco == config.decoder_on) {
+               if (speed)
+                       speed = speed - 1;
+               main_speed = speed;
+               return;
+       }
+
+       switch(loco) {
+       case 80:
+               switch (config.learn_mode) {
+               case LM_OFF:
+                       if (speed == 1)
+                               config.learn_mode = LM_EASY_WAIT_PRESS;
+                       break;
+               case LM_EASY_MODE:
+                       if ((speed == 1) && (alert_last == 0)) {
+                               config.learn_mode = LM_OFF;
+                               save_config();
+                               tone_good();
+                       }
+                       break;
+
+               case LM_EASY_WAIT_PRESS:
+                       if (speed != 1)
+                           config.learn_mode = LM_OFF;
+                       break;
+
+               default:
+                       break;
+               }
+               alert_last = (speed == 1);
+               break;
+
+       case 50:
+               if (speed)
+                       speed = speed - 1;
+
+               if (config.learn_mode == LM_EASY_MODE) {
+                       config.initial_pulse = speed;
+               }
+               break;
+       case 51:
+               if (speed)
+                       speed = speed - 1;
+
+               if (config.learn_mode == LM_EASY_MODE)
+                       config.on_duty_cycle[main_speed] = speed;
+               break;
+       }
+}
+
+void mm_key_cb(uint8_t decoder, uint8_t function, uint8_t raw_command)
+{
+       static uint8_t toggle_lock = 0;
+       uint8_t command;
+
+       static uint8_t last_raw = 0;
+       static uint8_t last_fkey = 0;
+
+       if ((function & 3) == 3) { /* F key hack! */
+               uint8_t diff;
+               diff =  last_raw ^ raw_command;
+               if (diff) {
+                       if (diff & 0x80)
+                               command = 4;
+                       else if (diff & 0x20)
+                               command = 3;
+                       else if (diff & 0x8)
+                               command = 2;
+                       else
+                               command = 1;
+                       command |= 0x80;
+                       if ((command == config.key_on)
+                           && (last_raw & ~raw_command))
+                               command = 0;
+                       last_raw = raw_command;
+               } else {
+                       return;
+               }
+       } else {
+               command = mm_lookup_key(raw_command);
+       }
+
+       switch(config.learn_mode) {
+       case LM_OFF:
+       default:
+               if ((decoder == config.decoder_on) &&
+                   (command == config.key_on)) { /* Primary key pressed */
+                       switch(config.op_mode) {
+                       case OM_MOMENTARY:
+                       case OM_DOUBLE:
+                               drive_on = 1;
+                               break;
+                       case OM_TOGGLE:
+                               if (!toggle_lock) {
+                                       drive_on = ~drive_on;
+                                       toggle_lock = 1;
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               if ((decoder == config.decoder_on) &&
+                   (command == 0)) {  /* Primary key released */
+                       switch(config.op_mode) {
+                       case OM_MOMENTARY:
+                               drive_on = 0;
+                               break;
+                       case OM_TOGGLE:
+                               toggle_lock = 0;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               break;
+
+       case LM_EASY_WAIT_PRESS:
+               if ((decoder == config.decoder_on) &&
+                   (command == config.key_on))
+                       config.learn_mode = LM_EASY_WAIT_UP;
+               return;
+
+       case LM_EASY_WAIT_UP:
+               if ((decoder == config.decoder_on) &&
+                   (command == 0)) {
+                       config.learn_mode = LM_EASY_MODE;
+                       tone_enter();
+               }
+               return;
+
+#ifdef HANDLE_OFF_KEY
+
+               if ((decoder == config.decoder_off) &&
+                   (command == config.key_off)) { /* Secondary "off" key pressed */
+                       switch(config.op_mode) {
+                       case OM_DOUBLE:
+                               drive_on = 0;
+                               break;
+                       case OM_TOGGLE:
+                       case OM_MOMENTARY:
+                       default:
+                               break;
+                       }
+               }
+#endif
+               break;
+
+       case LM_LEARN_ON_KEY:
+               if (command) {
+                       config.decoder_on = decoder;
+                       config.key_on     = command;
+                       if (config.op_mode == OM_DOUBLE)
+                               config.learn_mode = LM_LEARN_OFF_KEY;
+                       else
+                               config.learn_mode = LM_OFF;
+                       save_config();
+               }
+               break;
+#ifdef LEARN_ADVANCED
+       case LM_LEARN_OFF_KEY:
+               if (command) {
+                       config.decoder_off = decoder;
+                       config.key_off     = command;
+                       config.learn_mode = LM_OFF;
+                       save_config();
+               }
+               break;
+
+       case LM_LEARN_INITIAL:
+               if (drive_on) {
+                       if (command == 0)
+                               drive_on = 0;
+
+               } else {
+                       switch(command) {
+                       case 1:
+                               if (config.initial_pulse >= 10)
+                                       config.initial_pulse -= 10;
+                               else
+                                       config.initial_pulse = 0;
+                               save_config();
+                               drive_on = 1;
+                               break;
+                       case 2:
+                               if (config.initial_pulse <= 245)
+                                       config.initial_pulse += 10;
+                               else
+                                       config.initial_pulse = 255;
+                               save_config();
+                               drive_on = 1;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               break;
+
+       case LM_LEARN_DUTY:
+               if (drive_on) {
+                       if (command == 0)
+                               drive_on = 0;
+               } else {
+                       switch(command) {
+                       case 1:
+                               if (config.on_duty_cycle > 0)
+                                       config.on_duty_cycle -= 1;
+                               save_config();
+                               drive_on = 1;
+                               break;
+                       case 2:
+                               if (config.on_duty_cycle < 10)
+                                       config.on_duty_cycle += 1;
+                               save_config();
+                               drive_on = 1;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               break;
+
+       case LM_LEARN_OP_MODE:
+               switch(command) {
+               case 1:
+                       config.op_mode = OM_MOMENTARY;
+                       save_config();
+                       config.learn_mode = LM_OFF;
+                       break;
+               case 3:
+                       config.op_mode = OM_DOUBLE;
+                       save_config();
+                       config.learn_mode = LM_OFF;
+                       break;
+               case 5:
+                       config.op_mode = OM_TOGGLE;
+                       save_config();
+                       config.learn_mode = LM_OFF;
+                       break;
+               default:
+                       break;
+               }
+               break;
+#endif
+       }
+}
+
+
+/******************************************************************************
+ *
+ * main() - The main routine
+ *
+ */
+void shift(uint8_t mu);
+
+#ifdef DEBUG_DRIVE_LED
+#define MON_LED(val) setpin(PIN_LED, val)
+#else
+#define MON_LED(val)
+#endif
+
+#ifdef WITH_PWM
+#define DRIVE_OFF  {OCR1B = PWM_CYCLE; MON_LED(0);}
+#define DRIVE_ON   {OCR1B = PWM_CYCLE - config.on_duty_cycle[main_speed]; \
+               MON_LED(1);}
+#define DRIVE_FULL {OCR1B = 0; MON_LED(1);}
+#else
+#define DRIVE_OFF  {setpin(PIN_DRIVE, 0); MON_LED(0);}
+#define DRIVE_ON   {setpin(PIN_DRIVE, 1); MON_LED(1);}
+#define DRIVE_FULL {setpin(PIN_DRIVE, 1); MON_LED(1);}
+#endif
+
+int main(void) {
+       uint16_t i;
+
+#ifdef WITH_INITIAL_PULSE
+       uint8_t drive_last = 0;
+       uint8_t drive_slope = 0;
+#endif
+
+#ifdef USE_REGISTER_VARS
+       drive_on = 0;
+#endif
+       mm_init();
+       load_config();
+       setup_hw();
+
+       while(0) {
+               setpin(PIN_LED, 1);
+               _delay_ms(2);
+               _delay_ms(2);
+               trigger();
+               sei();
+       }
+       if (config.magic != EE_MAGIC) {
+               config.magic = EE_MAGIC;
+               config.op_mode = OM_MOMENTARY;
+               config.decoder_on = 1;
+               config.key_on = 1;
+               config.decoder_off = 1;
+               config.key_off = 2;
+               config.initial_pulse = 10;
+               config.on_duty_cycle[0] = 5;
+               config.learn_mode = LM_LEARN_ON_KEY;
+       }
+       sei();
+       while (1) {
+#ifdef MM_USE_QUEUE
+               dequeue_commands();
+#endif
+       drive_start:
+
+#ifdef WITH_INITIAL_PULSE
+               cli();
+               if (drive_on && !drive_last)
+                       drive_slope = 1;
+               else
+                       drive_slope = 0;
+               drive_last = drive_on;
+               sei();
+#endif
+               if (drive_on) {
+
+#ifdef WITH_INITIAL_PULSE
+                       if (drive_slope) {
+                               DRIVE_FULL;
+                               for (i = 0; i < config.initial_pulse; i++) {
+                                       _delay_ms(5);
+                               }
+                       }
+#endif
+                       DRIVE_ON;
+
+               } else {
+                       DRIVE_OFF;
+
+                       if (!config.learn_mode ||
+                           config.learn_mode > LM_LEARN_OP_MODE)
+                               continue;
+
+                       for (i = 0; i < config.learn_mode; i++) {
+                               setpin(PIN_LED, 1);
+                               snd_on();
+                               _delay_ms(10);
+                               setpin(PIN_LED, 0);
+                               snd_off();
+                               if (drive_on) goto drive_start;
+                               _delay_ms(135);
+                               if (drive_on) goto drive_start;
+                       }
+                       for (i = 0; i < 15 - config.learn_mode; i++) {
+                               if (drive_on) goto drive_start;
+                               _delay_ms(70);
+                       }
+               }
+               //              MCUCR |= _BV(SE);
+               //      sleep();
+       }
+       return 0;
+}
+
+/******************************************************************************
+ *                        The end :-)
+ */