--- /dev/null
+/******************************************************************************
+ *
+ * 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 :-)
+ */